news 2026/9/24 22:15:19

LanceDB Node.js AutoQuery 指南:基于表版本自动路由全文检索与向量搜索的构建器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LanceDB Node.js AutoQuery 指南:基于表版本自动路由全文检索与向量搜索的构建器
  • 向量数据库
  • 数据库
  • 人工智能
  • 后端

【免费下载链接】lancedb

Developer-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.

项目地址:https://gitcode.com/gh_mirrors/la/lancedb
点击查看免费下载

导读

AutoQuery@lancedb/lancedbNode.js SDK 中面向字符串搜索的自动路由查询构建器:它不要求开发者预先判断查询应该走全文检索(Full-Text Search)还是向量搜索(Vector Search),而是在每次执行时根据当前表版本(revision)的 schema 元数据动态决定路由。本文将完整讲解AutoQuery的触发入口、路由决策原理、全部可用的查询构建方法与执行/调试 API,并结合 query.ts 与 table.ts 的源码实现及 table.test.ts 的测试用例,帮助读者掌握"同一段搜索代码随表结构变化自动切换检索模式"的实战方案。

AutoQuery 是什么

在 AutoQuery 类文档 中,AutoQuery被定义为:

A builder for automatic string searches. Automatic search determines whether to use full-text or vector search from the table revision selected for each execution. This builder exposes the common operations supported by both query families.

即:自动字符串搜索构建器。它的核心特征是"每次执行时基于所选定的表版本决定使用全文搜索还是向量搜索",并对外暴露两类查询族(QueryVectorQuery)共同支持的通用操作。

从继承关系看,AutoQuery继承自StandardQueryBase<NativeQuery | NativeVectorQuery>(见 query.ts),因此它天然拥有全文查询与向量查询共享的构建能力;其内部持有inner: Query | VectorQuery | Promise<Query | VectorQuery>属性,最终真正执行时该属性会被解析为具体的原生查询对象。

需要强调的关键点:路由决策发生在执行时,而非构建时。也就是说,即使你在构建查询时表还没有 embedding 元数据,只要在真正执行(如调用toArray())前表被覆写为携带 embedding 函数的 schema,查询就会自动切换为向量搜索;反之亦然。这正是"automatic"一词的含义。

触发入口:Table.search 与 queryType 分派

AutoQuery并非由用户直接new出来(构造函数被标记为@hidden),而是通过Table.search()在默认参数下返回。在 table.ts 中:

search( query: string | IntoVector | MultiVector | FullTextQuery, queryType: string = "auto", ftsColumns?: string | string[], ): VectorQuery | Query | AutoQuery

其分派逻辑(table.ts)如下:

  1. query不是字符串且不是FullTextQuery实例,则视为向量输入,直接走vectorSearch(query)
  2. queryType === "fts",返回query().fullTextSearch(query, { columns: ftsColumns }),强制全文检索;
  3. queryType === "auto"(默认值):
    • 若传入的是FullTextQuery实例(如MatchQueryPhraseQuery等),直接构造全文查询;
    • 否则调用createAutoQuery(...)创建AutoQuery
  4. 其他queryType(如显式"vector")走nearestTo(queryPromise),其中查询向量由表上注册的 embedding 函数计算,若表未定义 embedding 函数则 reject"No embedding functions are defined in the table"(对应测试见 table.test.ts)。

因此最典型的用法就是:

import * as lancedb from "@lancedb/lancedb"; const db = await lancedb.connect("./.lancedb"); const table = await db.createTable("my_table", [{ text: "hello world" }]); // 默认 queryType 为 "auto",返回 AutoQuery const results = await table.search("hello").toArray();

底层路由原理:createAutoQuery 与表快照

AutoQuery的路由逻辑实现在createAutoQuery(query.ts),其核心是"每次执行时对表做一次快照并读取 schema 元数据":

const snapshotRoute = async (): Promise<RouteSnapshot> => { const snapshot = await table.querySnapshot(); const schema = tableFromIPC(await snapshot.schema()).schema; return { table: snapshot, embeddingMetadata: schema.metadata.get("embedding_functions"), }; };

路由判定规则非常直观:

  • 若表 schema 的元数据中没有embedding_functions,则内部构造inner.fullTextSearch({ query, columns }),走全文检索
  • 存在embedding_functions,则解析 embedding 函数并通过computeQueryEmbeddings(query)计算查询向量,然后走nearestToNative(...)向量搜索

也就是说:表上是否注册了 embedding 函数,决定了字符串搜索的路由方向。在 table.ts 中,embedding 元数据通过getRegistry().parseFunctions(new Map([["embedding_functions", metadata]]))解析,并调用首个 embedding 函数的computeQueryEmbeddings生成查询向量(当前实现仅支持单个 embedding 函数,源码中以TODO: Support multiple embedding functions标注)。

AutoQuery类本身(query.ts)使用"调用延迟绑定"模式实现"构建期统一、执行期路由":

  • 所有链式调用(如wherelimitselect)通过doCall被收集进calls数组,而不是立即作用到内部对象;
  • 每次执行(终端操作如toArraytoArrow)时getInner()先调用createInner()生成当前路由对应的原生查询,再把已收集的全部调用依次apply上去。

这样既保证了构建器可以被重复执行(每次执行都重新路由),又避免了在构建阶段就锁定查询类型。测试 table.test.ts 专门验证了这一点:同一个autoQuery对象在表被覆写为带 embedding schema 后执行,结果从全文检索语义切换到向量检索语义;同时验证了"构建器可复用执行时只新增快照调用、embedding 计算有缓存"的行为(snapshotCalls逐次 +1,而initCalls/queryCalls保持不增长)。

通用过滤与排序方法(StandardQueryBase 族)

AutoQuery继承了StandardQueryBase提供的以下通用方法(源码见 query.ts),这些方法同时被全文查询与向量查询支持:

where() / filter()

以 SQL 字符串作为过滤条件:

where(predicate: string): this

示例:

table.search("hello").where("x > 10").toArray(); table.search("hello").where("y > 0 AND y < 100").toArray(); table.search("hello").where("x > 5 OR y = 'test'").toArray();

两点使用要点(文档明确说明):

  • 过滤性能可以通过在过滤列上创建标量索引(scalar index)来提升;
  • 多次调用where时,多个过滤条件以逻辑 AND 合并,而不是后者替换前者。底层实现为inner.onlyIf(predicate)(见 query.ts)。

filter()where已废弃别名@deprecated Use where instead),新代码应直接使用where

fullTextSearch()

即使在AutoQuery上也可以显式叠加全文搜索约束(query.ts):

fullTextSearch(query: string | FullTextQuery, options?: Partial<FullTextSearchOptions>): this
  • query可以是普通字符串,也可以是 FullTextQuery 实例(如MatchQueryPhraseQueryBoostQueryMultiMatchQueryBooleanQuery,定义见 query.ts);
  • options.columns可以是单个字符串或字符串数组,指定参与全文检索的列;当传入FullTextQuery实例时直接使用其内部定义。

limit() 与 offset()

limit(limit: number): this offset(offset: number): this
  • 默认无 limit:文档明确指出,普通搜索(plain search)若不调用limit,将返回表中所有合法行;而向量搜索则默认limit为 10(见 query.ts 中nearestTo的注释)。
  • offset用于跳过指定数量的行后再返回结果,典型场景是分页。

orderBy()

按指定列排序(query.ts):

orderBy(ordering: ColumnOrdering | ColumnOrdering[]): this

ColumnOrdering(见 ColumnOrdering 接口)支持columnNameascending(默认true)、nullsFirst(默认false)字段,也可传入数组实现多列排序。注意:文档在useLsm中提示,MemWAL 表上的 LSM 扫描器不支持orderBy,此时需配合useLsm(false)使用。

fastSearch()

fastSearch(): this

跳过未索引数据的搜索,可显著加快查询速度,但会漏掉尚未建立索引的数据。文档明确建议:先用 Table#optimize 将全部未索引数据建立索引,再考虑使用fastSearch

useLsm():MemWAL 读路由控制

useLsm用于控制 MemWAL 表的读取路由(query.ts):

useLsm(enable: boolean): this

其语义如下(文档原文要点):

  • 默认(不调用):当表带有 MemWAL 写入配置(通过 Table#setLsmWriteSpec 设置)时,读取会路由到 LSM 扫描器,从而一并返回通过mergeInsertLSM 路径写入、尚未压实(compact)进基表的数据(包括 active/frozen 内存 memtable 与已刷新的 generation),并按主键去重;没有该配置的表则直接读取基表。
  • useLsm(true):强制使用 LSM 扫描器;若表没有 MemWAL 写入配置则直接报错。
  • useLsm(false):绕过 MemWAL,只读取基表(即使表带有配置)。
  • 限制:LSM 扫描器不支持所有查询形态(如 reranking、hybrid search、orderBy)。在 MemWAL 表上使用这些形态时,除非设置useLsm(false),否则会报错——因为只读基表会静默丢失尚未压实的 MemWAL 数据。

结果控制与执行方法(QueryBase 族)

select():列裁剪与动态列

select控制返回列(query.ts):

select(columns: string | string[] | Record<string, string> | Map<string, string>): this
  • 默认返回全部列,但这会显著影响延迟。LanceDB 以列式(columnar)存储,可以精细地只读取所需列,因此文档强调最佳实践是总是将查询裁剪到所需列
  • 传入字符串或字符串数组时,只返回这些列;
  • 传入Map<string, string>Record<string, string>可创建"动态列",键为返回列名,值为计算该列的 SQL 表达式。例如 SQL 的SELECT a + b AS combined, c等价于:
table.search("hello").select(new Map([["combined", "a + b"], ["c", "c"]])).toArray();
  • 列始终按给定顺序返回,即使该顺序与写入数据时的列序不同;
  • 文档提示:Record<string, string>(对象字面量)依赖Object.entries的插入顺序,容易出错,Map更可靠

withRowId():返回行 ID

withRowId(): this

在结果中返回行 ID 列,可用于跨查询匹配结果——例如同时执行全文检索与向量检索,再依据行 ID 做混合搜索(hybrid search)的融合。

execute() 与迭代

execute(options?)返回AsyncGenerator<RecordBatch<any>>,即 RecordBatch 的异步迭代器(query.ts)。文档指出:默认情况下 LanceDB 使用多线程计算结果,结果集较大时会同时处理多个 batch;但这种预读(readahead)是受限的,若消费速度慢会施加背压(backpressure),从而约束单次查询的最大内存占用。因此流式处理大批量结果时,可以直接for await消费。

toArray() 与 toArrow()

toArray(options?): Promise<any[]> // 收集结果为对象数组 toArrow(options?): Promise<ArrowTable> // 收集结果为 Arrow Table

两者均接受可选的QueryExecutionOptions(见 QueryExecutionOptions 接口,包含maxBatchLengthtimeoutMs等执行参数),是AutoQuery最常见的终端操作。

outputSchema():执行前预览输出结构

outputSchema(): Promise<Schema<any>>

返回本次查询将要输出结果的 Arrow Schema,可在真正执行前检查返回列的类型与名称(query.ts)。

查询计划调试:explainPlan 与 analyzePlan

这两个方法对排查"查询到底走了哪条路径、消耗在哪"非常关键,尤其适合验证AutoQuery的路由结果。

explainPlan()

explainPlan(verbose: boolean = false): Promise<string>

生成查询执行计划的文字说明;verbosetrue时提供更详细的信息。示例:

import * as lancedb from "@lancedb/lancedb"; const db = await lancedb.connect("./.lancedb"); const table = await db.createTable("my_table", [ { vector: [1.1, 0.9], id: "1" }, ]); const plan = await table.query().nearestTo([0.5, 0.2]).explainPlan();

analyzePlan()

analyzePlan(distributedMetrics?): Promise<string>

执行查询并返回带运行时指标(runtime metrics)的物理查询计划,适合性能分析与调试——它展示查询是如何被执行的,并包含各步骤的耗时、处理行数、I/O 统计等。参数distributedMetrics(类型见 AnalyzePlanDistributedMetrics)控制远程查询计划中分布式工作节点指标的展示方式,默认值为"aggregate"(聚合)。

官方文档给出的完整示例输出(向量查询路径,指标已内联):

AnalyzeExec verbose=true, metrics=[] ProjectionExec: expr=[id@3 as id, vector@0 as vector, _distance@2 as _distance], metrics=[output_rows=1, elapsed_compute=3.292µs] Take: columns="vector, _rowid, _distance, (id)", metrics=[output_rows=1, elapsed_compute=66.001µs, batches_processed=1, bytes_read=8, iops=1, requests=1] CoalesceBatchesExec: target_batch_size=1024, metrics=[output_rows=1, elapsed_compute=3.333µs] GlobalLimitExec: skip=0, fetch=10, metrics=[output_rows=1, elapsed_compute=167ns] FilterExec: _distance@2 IS NOT NULL, metrics=[output_rows=1, elapsed_compute=8.542µs] SortExec: TopK(fetch=10), expr=[_distance@2 ASC NULLS LAST], metrics=[output_rows=1, elapsed_compute=63.25µs, row_replacements=1] KNNVectorDistance: metric=l2, metrics=[output_rows=1, elapsed_compute=114.333µs, output_batches=1] LanceScan: uri=/path/to/data, projection=[vector], row_id=true, row_addr=false, ordered=false, metrics=[output_rows=1, elapsed_compute=103.626µs, bytes_read=549, iops=2, requests=2]

从该输出可以看出向量搜索的完整执行链:LanceScan(表扫描)→KNNVectorDistance(L2 距离计算)→SortExec(TopK 排序)→FilterExecGlobalLimitExecCoalesceBatchesExecTake(回取所需列)→ProjectionExec。若AutoQuery在无 embedding 元数据的表上路由到全文检索,计划树中的KNNVectorDistance会被全文检索相关算子取代,因此analyzePlan也是验证自动路由方向的实用手段。

实战模式与注意事项

综合文档与源码,使用AutoQuery时有几个值得注意的实践要点:

  1. 依赖 embedding 元数据自动路由:只要表 schema 带有embedding_functions元数据(通常通过LanceSchema+ 注册的EmbeddingFunction创建),table.search("文本")就会自动做向量搜索;否则退化为全文检索。创建表时使用 embedding 函数生成 schema 的示例可参考 embedding 模块 与LanceSchema(embedding/functions/LanceSchema.md)。
  2. 构建器可复用AutoQuery允许同一个构建器重复执行(每次执行重新取表快照并路由),适合需要跟随表版本演进的场景;配合只读一致性配置(如readConsistencyInterval)可观察到一致的快照行为。
  3. 全文检索需要索引:无 embedding 表上的自动路由是全文检索,若未在文本列创建 FTS 索引(Index.fts()),检索可能无法返回预期结果;创建索引可用 Table#createIndex。
  4. 明确指定类型:需要强制某种检索方式时,可在search(query, "fts", ftsColumns)search(query, "vector")中显式指定queryType,避免依赖自动路由;向量检索模式下若表未注册 embedding 函数会直接报错(见 table.test.ts)。
  5. 配合 where 与 select 控制开销:自动路由本身不改变列式存储的 I/O 优化特性,始终建议用select裁剪列、用where收紧范围,并用analyzePlan观察各阶段指标。

小结

AutoQuery是 LanceDB Node.js SDK 中"一个查询、两种模式"的抽象:它以表快照的embedding_functions元数据为路由依据,在每次执行时自动在全文检索与向量检索之间切换,并把两类查询共有的过滤、排序、分页、列裁剪、行 ID、MemWAL 路由与执行计划调试能力统一暴露给调用方。对于"同一份数据既可能带 embedding 又可能不带"的动态表结构场景,AutoQuery让应用代码无需随表结构变化而改动,值得作为字符串搜索的默认入口使用。

  • 向量数据库
  • 数据库
  • 人工智能
  • 后端

【免费下载链接】lancedb

Developer-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.

项目地址:https://gitcode.com/gh_mirrors/la/lancedb
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 22:14:45

Linux静态库与动态库制作全解析:从原理到实践

做Linux开发的人&#xff0c;迟早会碰上“动静态库”这几个字。不管是自己写工具函数给多个项目复用&#xff0c;还是接手别人的代码看到一堆libxxx.a、libxxx.so&#xff0c;又或者是编译软件时被undefined reference这种报错折磨——本质上都是同一个东西没搞透。这篇文章我直…

作者头像 李华
网站建设 2026/9/24 22:14:45

Linux静态库与动态库完全指南:制作、链接与踩坑实战

1. 项目核心&#xff1a;搞懂Linux里的静态库与动态库先抛个问题&#xff1a;你在Linux下写C程序&#xff0c;编译的时候是不是经常用-lm链接数学库&#xff1f;那你知道这个m库究竟长什么样&#xff0c;又是怎么被编译进去的吗&#xff1f;其实&#xff0c;libm.so和libm.a就是…

作者头像 李华
网站建设 2026/9/24 22:14:43

WorkBuddy智能体工作台实战:从订单抓取到本地自动化全攻略

1. 先说清楚&#xff1a;WorkBuddy 到底是什么&#xff0c;为什么突然大家都在聊最近不管是开发群还是跨境电商的运营群&#xff0c;都能看到有人在问 WorkBuddy 怎么安装、怎么配自定义指令&#xff0c;甚至还有人直接晒出自己用 WorkBuddy 搭的自动化工作流截图。我一开始以为…

作者头像 李华
网站建设 2026/9/24 22:14:43

LeetCode 865题解:后序遍历求所有最深节点的最小子树

第一次做 LeetCode 865「具有所有最深节点的最小子树」的时候&#xff0c;我一度被"最小子树"这四个字带偏了&#xff1a;以为要去找节点数量最少的那个子树&#xff0c;于是开始思考各种剪枝、统计节点数的方案。后来仔细一读题才发现&#xff0c;这里的"最小&…

作者头像 李华
网站建设 2026/9/24 22:14:42

Pro-Human AI 宣言的工程落地:从模型能力到系统行为的架构转型

Mustafa Suleyman 签 Pro-Human AI 宣言这件事&#xff0c;我第一反应不是跟着转发站队&#xff0c;而是下意识把手上的 agent 项目拉出来检查了一遍。Mustafa 是 DeepMind 联合创始人&#xff0c;AlphaGo 早期那批人之一&#xff0c;现在在微软负责整个 Microsoft AI&#xff…

作者头像 李华