语义搜索
学习如何通过含义而非精确关键词进行搜索。
语义搜索能够理解用户查询背后的含义,而不仅仅是精确匹配关键词。它利用机器学习捕捉查询背后的意图和上下文,处理语言中的细微差别如同义词、表达方式变化和词语关联。
何时使用语义搜索
语义搜索适用于那些需要深入理解和上下文分析才能提供相关结果的应用场景。客户支持或知识库搜索引擎就是很好的例子。用户通常会以各种方式表述他们的问题,传统的基于关键词的搜索可能无法总是检索到最有帮助的文档。通过语义搜索,系统能够理解查询背后的含义,并将其与相关解决方案或文章匹配,即使措辞不完全相同。
例如,在基于关键词的搜索系统中,用户搜索"增大显示屏上的文字大小"可能会错过标题为"如何在设置中调整字体大小"的文章。而语义搜索引擎能够理解查询背后的意图,正确匹配到相关文章,无论使用了什么具体术语。
您还可以将语义搜索与关键词搜索结合使用,获得两者的优势。更多详情请参阅混合搜索。
语义搜索的工作原理
语义搜索使用一种称为"嵌入向量"的中间表示来连接数据库记录与搜索查询。在语义搜索的上下文中,向量是一个数值列表。它们代表了文本的各种特征,并允许对不同文本片段进行语义比较。
理解嵌入向量的最佳方式是将它们绘制在图表上,其中每个嵌入向量都是一个点,其坐标就是向量中的数值。重要的是,嵌入向量的排列方式使得相似的概念彼此靠近,而不相似的概念则相距较远。更多细节请参阅什么是嵌入向量?
嵌入向量是通过语言模型生成的,并使用相似度度量来相互比较。语言模型经过训练能够理解语言的语义,包括语法、上下文以及单词之间的关系。它会为数据库中的内容和搜索查询都生成嵌入向量。然后使用相似度度量(通常是余弦相似度或点积等函数)来比较查询嵌入向量与文档嵌入向量(换句话说,就是测量它们在图表上的接近程度)。与查询嵌入向量最相似的文档嵌入向量对应的文档被认为是最相关的,并作为搜索结果返回。
嵌入模型
目前市面上有多种嵌入模型可供选择。Supabase 边缘函数内置支持 gte-small
模型。其他模型可以通过第三方 API 访问,例如 OpenAI,您可以在请求中发送文本并在响应中接收嵌入向量。还有一些模型可以在您自己的计算资源上本地运行,例如通过 JavaScript 实现的 Transformers.js。有关本地实现的更多信息,请参阅生成嵌入向量。
需要特别注意的是,在使用嵌入模型进行语义搜索时,所有嵌入比较必须使用相同的模型。比较由不同模型创建的嵌入向量会产生无意义的结果。
Postgres 中的语义搜索
要在 Postgres 中实现语义搜索,我们使用 pgvector
扩展——它能够高效存储和检索高维向量。这些向量是由嵌入模型生成的文本(或其他类型数据)的数值表示形式。
-
通过运行以下命令启用
pgvector
扩展:123create extension vectorwith schema extensions; -
创建用于存储嵌入向量的表:
12345create table documents ( id bigint primary key generated always as identity, content text, embedding vector(512));如果已有现有表,可以像这样添加向量列:
12alter table documentsadd column embedding vector(512);在这个示例中,我们创建了一个名为
embedding
的列,它使用新启用的vector
数据类型。括号中的向量大小表示嵌入向量的维度数。这里我们使用512,但请根据您的嵌入模型输出的维度数进行调整。
有关向量列的更多细节,包括如何生成和存储嵌入向量,请参阅向量列。
相似性度量
pgvector
支持3种计算嵌入向量间距离的运算符:
运算符 | 描述 |
---|---|
<-> | 欧几里得距离 |
<#> | 负内积 |
<=> | 余弦距离 |
这些运算符可直接用于SQL查询中,以检索与用户搜索查询最相似的记录。选择合适的运算符取决于您的需求。如果您的向量已归一化,内积(也称为点积)通常是速度最快的选择。
在Postgres中执行语义搜索最简单的方法是创建一个函数:
123456789101112131415-- 使用余弦距离(<=>)匹配文档create or replace function match_documents ( query_embedding vector(512), match_threshold float, match_count int)returns setof documentslanguage sqlas $$ select * from documents where documents.embedding <=> query_embedding < 1 - match_threshold order by documents.embedding <=> query_embedding asc limit least(match_count, 200);$$;
这里我们创建了一个match_documents
函数,它接受三个参数:
query_embedding
:为用户搜索查询生成的一次性嵌入向量。这里我们设置大小为512,但请根据您的嵌入模型输出的维度数进行调整。match_threshold
:嵌入向量之间的最小相似度。这是一个介于1和-1之间的值,其中1表示最相似,-1表示最不相似。match_count
:返回的最大结果数。注意如果match_threshold
导致候选结果较少,查询可能返回少于这个数字的结果。限制为200条记录以避免意外加重数据库负担。
在这个例子中,我们返回setof documents
并在整个查询中引用documents
。请根据您应用中的相关表进行调整。
您会注意到我们在查询中使用了余弦距离(<=>
)运算符。当您不确定嵌入向量是否归一化时,余弦距离是一个安全的默认选择。如果您确定它们已归一化(例如您的嵌入向量来自OpenAI),可以使用负内积(<#>
)以获得更好的性能:
123456789101112131415-- 使用负内积(<#>)匹配文档create or replace function match_documents ( query_embedding vector(512), match_threshold float, match_count int)returns setof documentslanguage sqlas $$ select * from documents where documents.embedding <#> query_embedding < -match_threshold order by documents.embedding <#> query_embedding asc limit least(match_count, 200);$$;
请注意由于<#>
是负值,我们在where
子句中相应地取负match_threshold
。有关不同运算符的更多信息,请参阅pgvector文档。
从应用程序调用
最后,您可以从应用程序中调用此函数。如果您使用的是 supabase-js
等 Supabase 客户端库,可以使用 rpc()
方法调用:
12345const { data: documents } = await supabase.rpc('match_documents', { query_embedding: embedding, // 传入查询向量 match_threshold: 0.78, // 为您的数据选择合适的阈值 match_count: 10, // 选择匹配数量})
您也可以直接从 SQL 调用此方法:
123456select *from match_documents( '[...]'::vector(512), -- 传入查询向量 0.78, -- 为您的数据选择合适的阈值 10 -- 选择匹配数量);
在这种情况下,您可能需要使用 Postgres 客户端库建立从应用程序到数据库的直接连接。最佳实践是在执行查询前对参数进行参数化处理。
后续步骤
随着数据库规模扩大,您需要在向量列上创建索引以保持快速查询速度。请参阅向量索引了解不同类型的索引及其工作原理的详细指南。