Mastra OpenSearch 向量存储接入指南:从配置演进到过滤查询的源码级解析
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
导读
本文围绕@mastra/opensearch这个 Mastra 官方向量存储适配器展开,结合 stores/opensearch/CHANGELOG.md 中记录的版本演进脉络与 stores/opensearch/src/vector/index.ts 的实际实现,完整讲解如何在 Mastra 应用中以 OpenSearch 作为 RAG 向量库:从本地环境搭建、客户端配置(url到node的迁移)、索引创建、向量写入与相似度查询,到元数据过滤操作符体系、批量更新删除与结构化错误处理。读完本文,你将掌握 OpenSearchVector 的完整 API 用法、过滤器的底层翻译原理,以及 1.0 版本以来的全部破坏性变更细节,能够直接在项目中落地一套可运行的 OpenSearch 向量检索方案。
OpenSearchVector 在 Mastra 中的定位
Mastra 是一个面向 AI 应用与 Agent 的现代 TypeScript 框架。在其stores/目录下,官方为十余种数据库提供了统一的向量存储适配器,@mastra/opensearch是其中之一,对应目录为 stores/opensearch。该包的核心类是OpenSearchVector,继承自@mastra/core的MastraVector基类,因此天然支持 Mastra 统一抽象的createIndex、upsert、query、updateVector、deleteVector、deleteVectors、listIndexes、describeIndex、deleteIndex等向量操作接口。
从包配置(stores/opensearch/package.json)可以看到几个关键约束:
- 依赖关系:运行时仅依赖
@opensearch-project/opensearch(当前为^3.6.0),以@mastra/core作为 peer 依赖(要求>=1.0.0-0 <2.0.0-0),与核心版本严格对齐; - 运行环境:
engines.node要求>=22.13.0——这是 1.0.0 版本起全框架统一提升的最低 Node 版本要求; - 发布形态:
files仅包含dist目录,且在 1.1.1 版本中移除了随包分发的CHANGELOG.md,以减小包体积。
本地环境快速搭建
测试与本地开发时,仓库为 OpenSearch 提供了开箱即用的容器编排文件 stores/opensearch/docker-compose.yaml,只需一条命令即可拉起单节点服务:
services: opensearch: image: opensearchproject/opensearch:latest ports: - '9200:9200' environment: - discovery.type=single-node - OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m - bootstrap.memory_lock=true - DISABLE_SECURITY_PLUGIN=true # 本地开发关闭安全插件,方便免认证访问 ulimits: memlock: { soft: -1, hard: -1 }docker compose up -d在 stores/opensearch/package.json 的脚本中可以看到,pretest正是先执行docker compose up -d,然后轮询http://localhost:9200/_cluster/health直到集群就绪后才运行 vitest,posttest再docker compose down -v清理,保证测试环境的可重复性。生产环境则建议按你的实际部署方式(自建集群、托管服务等)配置节点地址与认证信息。
安装包本身只需要一行:
npm install @mastra/opensearch配置方式的核心演进:从url到node
stores/opensearch/CHANGELOG.md 中 1.0.0 版本记录了一次影响所有向量存储的 Minor 变更——"Aligned vector store configuration with underlying library APIs"(对齐底层库 API 的配置)。这次变更直接决定了今天OpenSearchVector的构造方式,是理解本包配置语义的关键。
变更动机
此前,每个向量存储各自定义了一套只暴露底层库部分选项的配置类型,用户无法直接使用认证(authentication)、SSL、压缩(compression)、自定义请求头(custom headers)等高级特性,除非自己手工创建底层客户端实例。从 1.0.0 起,配置类型改为直接扩展底层库的类型,所有选项全部透传可用。
对 @mastra/opensearch 的破坏性变更
配置项url被重命名为node(与@opensearch-project/opensearch官方ClientOptions的字段名一致),同时开放全部ClientOptions。CHANGELOG 中的迁移示例完整如下:
// Before(1.0.0 之前) new OpenSearchVector({ id: 'my-vector', url: 'http://localhost:9200' }); // After(1.0.0 起) new OpenSearchVector({ id: 'my-vector', node: 'http://localhost:9200' }); // With authentication(现在可以直接在构造器中完成认证配置) new OpenSearchVector({ id: 'my-vector', node: 'https://localhost:9200', auth: { username: 'admin', password: 'admin' }, ssl: { rejectUnauthorized: false }, });这一设计在源码中得到直接印证。在 stores/opensearch/src/vector/index.ts 中,配置类型被定义为:
export type OpenSearchVectorConfig = ClientOptions & { id: string };构造函数将id拆出交给基类super({ id }),其余选项原样传给官方客户端:
constructor({ id, ...clientOptions }: OpenSearchVectorConfig) { super({ id }); this.client = new OpenSearchClient(clientOptions); }也就是说,官方 OpenSearch JS 客户端支持的一切选项——node、auth、ssl、compression、headers、requestTimeout、maxRetries等——都可以直接传入构造器,无需再自建客户端实例。测试代码(stores/opensearch/src/vector/index.test.ts)也验证了new OpenSearchVector({ node, id: 'opensearch-test' })的用法。
一次影响面广的配置对齐
同一 PR 还同步调整了其他适配器:@mastra/libsql将connectionUrl重命名为url;@mastra/pinecone移除了environment参数、改用controllerHostUrl;@mastra/clickhouse增加request_timeout、compression、keep_alive、database等选项支持;多个存储包将console.warn替换为结构化日志。从 CHANGELOG 可以推断,这是一次全框架统一的"配置直通底层库"设计决策,OpenSearchVector只是其中一环。
索引创建:映射、度量与 HNSW 参数
createIndex负责在 OpenSearch 中创建一个启用 k-NN 能力的索引。源码实现(stores/opensearch/src/vector/index.ts)对三个核心参数有明确约束:
| 参数 | 说明 | 默认值/约束 |
|---|---|---|
indexName | 索引名(对应集合名) | 必填,字符串 |
dimension | 向量维度 | 必填,必须是正整数,否则抛出MASTRA_VECTOR_OPENSEARCH_CREATE_INDEX_INVALID_ARGS错误 |
metric | 距离度量 | 默认'cosine',可选'cosine' \| 'euclidean' \| 'dotproduct' |
值得注意的是度量名称与 OpenSearch 底层空间类型的映射,源码中通过METRIC_MAPPING完成:
const METRIC_MAPPING = { cosine: 'cosinesimil', euclidean: 'l2', dotproduct: 'innerproduct', } as const;describeIndex返回统计信息时,则用反向映射REVERSE_METRIC_MAPPING把 OpenSearch 的空间类型还原为 Mastra 的度量名。也就是说,Mastra 层的'cosine'对应 OpenSearch 的cosinesimil,'euclidean'对应l2,'dotproduct'对应innerproduct。
生成的索引映射如下(来自源码createIndex的请求体):
{ "settings": { "index": { "knn": true } }, "mappings": { "properties": { "metadata": { "type": "object" }, "id": { "type": "keyword" }, "embedding": { "type": "knn_vector", "dimension": 3, "method": { "name": "hnsw", "space_type": "cosinesimil", "engine": "faiss", "parameters": { "ef_construction": 128, "m": 16 } } } } } }即默认使用HNSW 算法 + faiss 引擎,ef_construction: 128、m: 16。这些参数目前是写死的内置默认值,适合绝大多数 RAG 场景;如需调优索引级参数,可以根据 k-NN 插件文档在 OpenSearch 侧自行调整映射。
重复创建索引的幂等处理
createIndex对"索引已存在"做了专门的幂等处理(源码与测试均有覆盖):如果创建请求因already exists失败,会调用validateExistingIndex校验已存在索引的维度与度量;维度相同则静默通过(仅记录日志),维度不同则抛出明确错误。这一点由 stores/opensearch/src/vector/index.test.ts 中的duplicate-test用例完整验证:重复创建相同维度不抛错、相同维度不同 metric 不抛错、不同维度则抛Index "... already exists with 768 dimensions, but 769 dimensions were requested。这在幂等部署、重跑初始化脚本时非常实用。
写入向量:upsert 与批量 API
upsert用于向索引写入或更新向量,签名与参数如下:
async upsert({ indexName, vectors, metadata = [], ids }: UpsertVectorParams): Promise<string[]>vectors:二维数组,每一行是一个向量;metadata:与向量一一对应的元数据对象数组(可选,缺省为空数组);ids:可选的 ID 数组;不传时自动用crypto.randomUUID()生成;- 返回值:本次写入的向量 ID 数组。
实现细节(stores/opensearch/src/vector/index.ts):
- 调用
validateUpsert统一校验输入; - 通过
describeIndex读取已建索引的维度,再用validateVectorDimensions校验每个向量的长度与索引维度一致,防止"向量维度不匹配索引维度"这类静默数据错误; - 将每个文档组装为
{ id, embedding, metadata }结构,通过 OpenSearchbulk API一次性写入并refresh: true立即可查。
典型写入示例(来自 stores/opensearch/README.md):
const vectorStore = new OpenSearchVector({ id: 'my-vector', node: 'http://localhost:9200' }); // 创建索引 await vectorStore.createIndex({ indexName: 'my-collection', dimension: 3, metric: 'cosine' }); // 写入向量 const vectors = [ [0.1, 0.2, 0.3], [0.3, 0.4, 0.5], ]; const metadata = [{ text: 'doc1' }, { text: 'doc2' }]; const ids = await vectorStore.upsert({ indexName: 'my-collection', vectors, metadata });相似度查询与元数据过滤
query是向量检索的核心入口:
async query({ indexName, queryVector, filter, topK = 10, includeVector = false, }): Promise<QueryResult[]>参数语义:queryVector为查询向量;topK返回条数(默认 10);filter为元数据过滤条件;includeVector决定返回结果中是否携带原始向量(默认不携带)。返回的QueryResult包含id、score、metadata,以及可选的vector字段。
底层查询通过bool查询将 k-NN 检索与过滤条件组合:must子句执行knn向量检索(k即topK),filter子句承载翻译后的元数据过滤 DSL,_source仅取id、metadata、embedding三个字段。完整的调用示例:
const results = await vectorStore.query({ indexName: 'my-collection', queryVector: [0.1, 0.2, 0.3], topK: 10, filter: { text: { $eq: 'doc1' } }, // 元数据过滤 includeVector: false, });queryVector 必填:结构化错误替代 SDK 困惑
在 1.0.1 版本(见 CHANGELOG)中,所有要求向量参与查询的存储统一新增了运行时错误提示:当queryVector缺失时,不再抛出令人困惑的 SDK 层错误,而是抛出结构化的MastraError(ErrorCategory.USER),明确说明"OpenSearch 查询不支持仅元数据查询"。源码中的对应实现:
if (!queryVector) { throw new MastraError({ id: createVectorErrorId('OPENSEARCH', 'QUERY', 'MISSING_VECTOR'), text: 'queryVector is required for OpenSearch queries. Metadata-only queries are not supported by this vector store.', domain: ErrorDomain.STORAGE, category: ErrorCategory.USER, details: { indexName }, }); }这是 CHANGELOG 中"提升可观测性"理念的一个典型落地:把不可预期的底层报错转化为语义清晰、可程序化处理的领域错误。
元数据过滤操作符体系:MongoDB 风格到 OpenSearch DSL
过滤是向量检索中"先粗筛再精排"的关键能力。OpenSearchVector的过滤器采用与 Mastra 其他存储一致的 MongoDB 风格操作符,但根据 OpenSearch 的能力做了裁剪与映射。底层翻译逻辑由 stores/opensearch/src/vector/filter.ts 的OpenSearchFilterTranslator完成,它继承自@mastra/core的BaseFilterTranslator,并把过滤条件编译为 OpenSearch Query DSL。
支持的操作符清单
从源码getSupportedOperators与过滤提示词 stores/opensearch/src/vector/prompt.ts 可以确认,OpenSearchVector支持的操作符如下:
| 类别 | 操作符 | 说明与示例 |
|---|---|---|
| 基础比较 | $eq | 精确匹配,{ "category": "electronics" }或{ "category": { "$eq": "electronics" } } |
$ne | 不相等,{ "category": { "$ne": "electronics" } } | |
$gt/$gte/$lt/$lte | 数值/日期范围,{ "price": { "$gt": 100 } } | |
| 数组 | $in | 匹配数组中任一值,{ "category": { "$in": ["electronics", "books"] } } |
$nin | 不匹配数组中任一值 | |
$all | 必须包含数组中全部值,{ "tags": { "$all": ["premium", "sale"] } } | |
| 逻辑 | $and | 逻辑与,多条件并列时隐式生效 |
$or | 逻辑或 | |
$not | 逻辑非(仅对象、不可为空) | |
| 元素 | $exists | 字段存在性,{ "rating": { "$exists": true } } |
| 正则 | $regex | ECMAScript 语法正则,仅支持字符串字段,{ "name": { "$regex": "^Sam.*son$" } } |
明确不支持的操作符包括$nor、$elemMatch、$options(源码通过Omit<OperatorValueMap, '$options' | '$nor' | '$elemMatch'>在类型层面直接排除),这与测试中supportsNorOperator: false、supportsElemMatch: false的声明一致。
翻译规则与边界情况
结合 stores/opensearch/src/vector/filter.test.ts 的断言,翻译器有几个值得注意的行为:
- 字段前缀:所有字段条件都会被翻译为
metadata.<字段>路径,例如{ field: 'value' }变成term: { 'metadata.field.keyword': 'value' }; - keyword 后缀:字符串值(含全字符串数组)会追加
.keyword后缀以命中 keyword 字段做精确匹配;数值、布尔值不加; - 多条件组合:多个顶层字段自动用
bool.must组合(隐式 AND);$and→bool.must,$or→bool.should,$not→bool.must_not; - 空数组语义:空
$and匹配全部(match_all)、空$or匹配空集、空$nin匹配全部、空$all匹配空集,"空数组条件被优雅处理"; - null 语义:
$eq: null翻译为"字段不存在"(must_not exists),$ne: null翻译为"字段存在"(exists); - 数值范围合并优化:同一字段上的多个数值比较操作符(如
$gte与$lte并用)会被合并成单个range查询(canOptimizeToRangeQuery→createRangeQuery),例如{ price: { "$gte": 100, "$lte": 1000 } }直接编译为一个 range DSL; - 正则翻译:带
^/$锚点的模式转换为wildcard查询,其余转换为regexp查询;含换行的模式退化为match查询; - 嵌套字段:支持点号路径(
user.profile.age),且字符串子字段同样应用 keyword 后缀。
为了让 LLM/Agent 在构造过滤条件时不越界,仓库还内置了一段系统提示词 stores/opensearch/src/vector/prompt.ts(OPENSEARCH_PROMPT),明确定义允许的操作符、语法约束与反例(如禁止顶层直接使用$gt、逻辑操作符内部必须包字段条件等),并声明"遇到不支持的操作符就整体拒绝该过滤条件"。从 CHANGELOG 0.10.1 版本的记录"Added prompt for OpenSearchVector"可以看出这是早期就引入的设计。
一个完整的多条件组合示例(来自 prompt.ts):
{ "$and": [ { "category": { "$in": ["electronics", "computers"] } }, { "price": { "$gte": 100, "$lte": 1000 } }, { "tags": { "$all": ["premium"] } }, { "rating": { "$exists": true, "$gt": 4 } }, { "$or": [ { "stock": { "$gt": 0 } }, { "preorder": true } ] }, { "name": { "$regex": "^Sam.*son$" } } ] }数据管理:按 ID 或按过滤器更新与删除
自 1.0.0 起(CHANGELOG 记录 "Add new deleteVectors, updateVector by filter"),OpenSearchVector支持按 ID 与按过滤器两种维度的更新与批量删除。
updateVector:按 ID 或按过滤器更新
updateVector接受{ indexName, id, update }或{ indexName, filter, update }两种形态,update可携带vector与/或metadata。源码中的校验逻辑非常严谨:
id与filter互斥,同时提供抛MUTUALLY_EXCLUSIVE错误;update中必须至少提供vector或metadata之一,否则抛NO_UPDATES;- 空过滤器不允许,抛
EMPTY_FILTER;两者都缺抛NO_TARGET。
按 ID 更新时(updateVectorById),实现先读取现有文档,把新值与_source合并后整体写回(更新向量前同样校验维度);按过滤器更新时(updateVectorsByFilter),则利用 OpenSearch 的updateByQuery+ Painless 脚本(ctx._source.embedding = params.embedding; ctx._source.metadata = params.metadata)批量更新所有命中文档。
deleteVector 与 deleteVectors
deleteVector({ indexName, id }):删除单个文档,对 404(文档不存在)静默容忍,不抛错;deleteVectors({ indexName, ids })或deleteVectors({ indexName, filter }):按 ID 批量删除使用 bulk API;按过滤器删除使用deleteByQuery。与更新一致,ids与filter互斥、空数组/空过滤器均抛结构化错误。
此外还有listIndexes()(通过cat.indices列出全部索引)与deleteIndex({ indexName })(删除整个索引,失败时通过结构化日志记录)。
错误处理与可观测性
从 1.0.0 版本开始,所有存储与向量存储统一使用createVectorErrorId/createStorageErrorId辅助函数(CHANGELOG 记录,PR 10913),错误 ID 遵循统一模式:
MASTRA_VECTOR_{STORE}_{OPERATION}_{STATUS}对 OpenSearch 即MASTRA_VECTOR_OPENSEARCH_*,例如MASTRA_VECTOR_OPENSEARCH_QUERY_MISSING_VECTOR、MASTRA_VECTOR_OPENSEARCH_CREATE_INDEX_INVALID_ARGS。每个MastraError都携带ErrorDomain.STORAGE域与ErrorCategory分类:
ErrorCategory.USER:参数级错误(维度非法、queryVector 缺失、互斥参数、空过滤器等),提示用户修正调用方式;ErrorCategory.THIRD_PARTY:底层 OpenSearch 调用失败(建索引、写入、查询、删除等),错误对象会作为 cause 包裹在MastraError中,并附带details(如indexName、topK、vectorCount)便于定位。
deleteIndex失败时还会通过实例的logger(this.logger?.error/trackException)记录错误与异常追踪,体现框架统一的可观测性接入点。这种"一致的错误 ID 模式"(CHANGELOG 原话)让跨存储的错误追踪与调试体验保持一致。
测试保障:共享套件与本地验证
OpenSearchVector的可靠性由两层测试保障(stores/opensearch/src/vector/index.test.ts):
- 共享向量测试套件:通过
createVectorTestSuite挂接 Mastra 的统一向量存储测试集,覆盖各存储通用的行为契约,并声明 OpenSearch 的能力边界(supportsRegex: false、supportsNorOperator: false、supportsElemMatch: false、supportsSize: false、supportsAdvancedNotSyntax: false); - 专属错误处理用例:如重复建索引的幂等性、维度不一致的报错信息等。
结合 stores/opensearch/src/vector/filter.test.ts 对翻译器的逐操作符断言(空过滤、term/bool/range/terms生成、嵌套对象、日期序列化、$and/$or/$not、$in/$nin/$all、$exists、$regex等),过滤 DSL 的生成行为基本都有测试锚点,可放心作为实现参照。运行方式即npm test(会自动起 docker compose 并在结束清理)。
版本与维护节奏
从 stores/opensearch/CHANGELOG.md 可以看到清晰的生命周期:
- 0.1.0:初版,引入 OpenSearch 向量存储支持;
- 0.x 系列:围绕
@mastra/core的 peer 依赖对齐、命名参数化改造(0.10.0 移除废弃的位置参数)、过滤元数据类型(0.11.0)、Mastra 错误标准化(0.11.0)、SDK 依赖升级(@opensearch-project/opensearch从^3.4.0一路升至^3.6.0); - 1.0.0(Major):标记稳定;要求每个 primitive 设置
id;最低 Node 版本提升至 22.13.0;配置对齐底层库 API(OpenSearch 的url→node);新增按过滤器的deleteVectors/updateVector;错误 ID 标准化;包内嵌文档(dist/docs/下的 SKILL.md、SOURCE_MAP.json 等,供 Agent 直接阅读); - 1.0.1:为强制要求
queryVector的存储补充结构化运行时错误; - 1.0.2:依赖升级至
@opensearch-project/opensearch@^3.6.0; - 1.0.5:针对供应链事件("easy-day-js" 事件)的安全补丁发布,重新发布干净版本并推进
latestdist-tag; - 1.1.0 / 1.1.1:例行版本同步与包体优化(npm 包中移除 CHANGELOG、README 更新)。
可以看到该包始终与@mastra/core保持严格的版本对齐(每个版本都带@mastra/core的对应依赖更新),升级时务必同时升级 core,避免 peer 依赖冲突。
小结
@mastra/opensearch是 Mastra 向量存储生态中与 OpenSearch 对接的标准实现:它以"配置直通底层客户端"的设计承接了官方 OpenSearch JS 客户端的全部能力,用统一的MastraVector接口封装了建索引、写入、k-NN 检索、过滤查询、按 ID/过滤器更新删除等操作,并通过 MongoDB 风格过滤操作符 + DSL 翻译器把元数据过滤能力桥接到 OpenSearch Query DSL。对于需要在自建 OpenSearch 集群之上构建 RAG 应用的团队,可以参照本文的安装、配置(注意node字段)、建索引与查询示例快速上手,同时结合 stores/opensearch/src/vector/index.ts、stores/opensearch/src/vector/filter.ts 与 stores/opensearch/README.md 持续深入。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考