跳转到内容

全文搜索(原生FTS)

LanceDB通过Lance提供全文搜索支持,使您能够在检索解决方案中集成基于关键词的搜索(基于BM25算法)。

注意

Python SDK默认使用基于tantivy的全文搜索(FTS),需传入use_tantivy=False参数以启用原生全文搜索功能。

示例

假设我们有一个名为my_table的LanceDB表,其字符串列text需要通过关键词搜索进行索引和查询,必须先创建FTS索引才能通过关键词进行搜索。

import lancedb

from lancedb.index import FTS

uri = "data/sample-lancedb"
db = lancedb.connect(uri)

table = db.create_table(
    "my_table_fts",
    data=[
        {"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"},
        {"vector": [5.9, 26.5], "text": "There are several kittens playing"},
    ],
)

# passing `use_tantivy=False` to use lance FTS index
# `use_tantivy=True` by default
table.create_fts_index("text", use_tantivy=False)
table.search("puppy").limit(10).select(["text"]).to_list()
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
# ...
import lancedb

from lancedb.index import FTS

uri = "data/sample-lancedb"
async_db = await lancedb.connect_async(uri)

async_tbl = await async_db.create_table(
    "my_table_fts_async",
    data=[
        {"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"},
        {"vector": [5.9, 26.5], "text": "There are several kittens playing"},
    ],
)

# async API uses our native FTS algorithm
await async_tbl.create_index("text", config=FTS())
await (await async_tbl.search("puppy")).select(["text"]).limit(10).to_list()
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
# ...
import * as lancedb from "@lancedb/lancedb";
const uri = "data/sample-lancedb"
const db = await lancedb.connect(uri);

const data = [
{ vector: [3.1, 4.1], text: "Frodo was a happy puppy" },
{ vector: [5.9, 26.5], text: "There are several kittens playing" },
];
const tbl = await db.createTable("my_table", data, { mode: "overwrite" });
await tbl.createIndex("text", {
    config: lancedb.Index.fts(),
});

await tbl
    .search("puppy", "fts")
    .select(["text"])
    .limit(10)
    .toArray();
let uri = "data/sample-lancedb";
let db = connect(uri).execute().await?;
let initial_data: Box<dyn RecordBatchReader + Send> = create_some_records()?;
let tbl = db
    .create_table("my_table", initial_data)
    .execute()
    .await?;
tbl
    .create_index(&["text"], Index::FTS(FtsIndexBuilder::default()))
    .execute()
    .await?;

tbl
    .query()
    .full_text_search(FullTextSearchQuery::new("puppy".to_owned()))
    .select(lancedb::query::Select::Columns(vec!["text".to_owned()]))
    .limit(10)
    .execute()
    .await?;

默认情况下,它会在所有已建立索引的列上进行搜索,因此在存在多个索引列时非常有用。

如果要指定搜索的列,请传入 fts_columns="text"

注意

如果搜索输入的类型是str,LanceDB会自动在现有的全文搜索索引上进行搜索。如果提供向量作为输入,LanceDB则会转而搜索近似最近邻索引。

分词

默认情况下,文本会通过标点符号和空格进行分词,并过滤掉长度超过40个字符的单词,同时将所有单词转为小写。

词干提取通过将单词还原为其词根形式(例如将"running"转换为"run")有助于改善搜索结果。LanceDB支持多种语言的词干提取功能,您可以通过模式tokenizer_name="{language_code}_stem"指定分词器名称来启用词干提取,例如英语使用en_stem

例如,要为英文启用词干提取:

table.create_fts_index("text", tokenizer_name="en_stem", replace=True)
await async_tbl.create_index(
    "text", config=FTS(language="English", stem=True, remove_stop_words=True)

目前支持以下语言

分词器是可定制的,您可以指定分词器如何分割文本,以及如何过滤单词等。

例如,对于带重音符号的语言,您可以指定分词器使用ascii_folding来去除重音,例如将'é'转换为'e':

table.create_fts_index(
    "text",
    use_tantivy=False,
    language="French",
    stem=True,
    ascii_folding=True,
    replace=True,
)
await async_tbl.create_index(
    "text", config=FTS(language="French", stem=True, ascii_folding=True)
)

筛选

LanceDB全文搜索支持通过条件筛选搜索结果,同时支持预筛选和后筛选。

可以通过熟悉的where语法来调用。

使用预过滤:

table.search("puppy").limit(10).where("text='foo'", prefilter=True).to_list()
await (await async_tbl.search("puppy")).limit(10).where("text='foo'").to_list()
await tbl
.search("puppy")
.select(["id", "doc"])
.limit(10)
.where("meta='foo'")
.prefilter(true)
.toArray();
table
    .query()
    .full_text_search(FullTextSearchQuery::new("puppy".to_owned()))
    .select(lancedb::query::Select::Columns(vec!["doc".to_owned()]))
    .limit(10)
    .only_if("meta='foo'")
    .execute()
    .await?;

使用后置过滤:

table.search("puppy").limit(10).where("text='foo'", prefilter=False).to_list()
await (
    (await async_tbl.search("puppy"))
    .limit(10)
    .where("text='foo'")
    .postfilter()
    .to_list()
)
await tbl
.search("apple")
.select(["id", "doc"])
.limit(10)
.where("meta='foo'")
.prefilter(false)
.toArray();
table
    .query()
    .full_text_search(FullTextSearchQuery::new(words[0].to_owned()))
    .select(lancedb::query::Select::Columns(vec!["doc".to_owned()]))
    .postfilter()
    .limit(10)
    .only_if("meta='foo'")
    .execute()
    .await?;

短语查询与术语查询对比

警告

基于Lance的全文检索不支持使用布尔运算符ORAND进行查询。

对于全文搜索,您可以指定短语查询如"the old man and the sea", 或者关键词搜索查询如old man sea。有关关键词查询语法的更多详情,请参阅Tantivy的查询解析规则

要搜索短语,索引创建时必须设置with_position=True参数:

table.create_fts_index("text", use_tantivy=False, with_position=True, replace=True)
await async_tbl.create_index("text", config=FTS(with_position=True))

这将允许您搜索短语,但同时也会显著增加索引大小和索引时间。

增量索引

LanceDB 支持增量索引,这意味着您可以在不重新索引整个表的情况下向表中添加新记录。

这可以提高查询效率,尤其是当表数据量很大而新增记录相对较少时。

table.add([{"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"}])
table.optimize()
await async_tbl.add([{"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"}])
await async_tbl.optimize()
await tbl.add([{ vector: [3.1, 4.1], text: "Frodo was a happy puppy" }]);
await tbl.optimize();
let more_data: Box<dyn RecordBatchReader + Send> = create_some_records()?;
tbl.add(more_data).execute().await?;
tbl.optimize(OptimizeAction::All).execute().await?;

注意

在创建FTS索引后新增的数据仍会出现在搜索结果中(增量索引构建过程中),但由于需要对未索引部分进行平面搜索,会导致延迟增加。LanceDB Cloud自动执行此合并流程,将对搜索速度的影响降至最低。