在supabase中实现关键词检索和语义检索

在AI应用中,一个经常出现的场景是进行检索。关于检索,一般有基于关键词的检索、基于语义的向量检索以及结合两种方式的混合检索。一般的数据库基本上都能够支持关键词检索(全文检索),而向量检索一般有相关的向量数据库做特定的优化。那么,如果我们的场景下想要实现这三种检索能力,并且我们的技术栈中已经使用了supabase,那么要怎么基于supabase来实现这些能力呢?是否需要引入额外的向量数据库呢?本文将描述如何在supabase中实现这些检索(关于supabase,可以参考我之前写过的文章

Supabase中的语义检索

语义搜索主要解读的是用户查询背后的含义,而非确切的关键词。在某些场景下关键词描述可能不够准确,我们可能希望检索一些关键词背后的实际语义,这个时候就可以使用语义检索。从原理上来看,做语义检索时需要对已有的文档和查询进行嵌入,将它们转换成一个高维的向量,然后在向量空间中基于距离来衡量相似度,从而找到最相关的内容。在supabase中,为了实现语义检索,可以使用一个扩展:pgvector。接下来来看下官网的示例:

开启扩展

sql 复制代码
create extension vector with schema extensions;

创建数据表

sql 复制代码
create table documents (
  id bigint primary key generated always as identity,
  content text,
  embedding vector(512)
);

如果已经有表了,可以通过以下方式增加一个列。

sql 复制代码
alter table documents
add column embedding vector(512);

创建基于相似度度量的函数

最简单的进行语义检索的方法就是在数据库中创建一个函数:

sql 复制代码
-- Match documents using cosine distance (<=>)
create or replace function match_documents (
  query_embedding vector(512),
  match_threshold float,
  match_count int
)
returns setof documents
language sql
as $$
  select *
  from documents
  where documents.embedding <=> query_embedding < 1 - match_threshold
  order by documents.embedding <=> query_embedding asc
  limit least(match_count, 200);
$$;

可以看到这里比较相似度时,用的是<=>,这表示相似度度量的距离是cosine distance。除了这种距离度量之外,还包括以下度量。

运算符 描述 使用场景
<-> Euclidean distance描述向量空间中两点之间的直线距离 低维空间中高精度匹配
<#> negative inner product两个向量的内积的负值 向量已做归一化时处理最快
<=> cosine distance两个向量之间的夹角来计算的 未确认向量是否做归一化时安全的默认选择

执行检索

如果是在程序中调用,可以使用客户端的方式来进行调用。

csharp 复制代码
const { data: documents } = await supabase.rpc('match_documents', {
  query_embedding: embedding, // pass the query embedding
  match_threshold: 0.78, // choose an appropriate threshold for your data
  match_count: 10, // choose the number of matches
})

如果是在supabase的sql界面中,则也可以按照如下方式调用。

sql 复制代码
select *
from match_documents(
  '[...]'::vector(512), -- pass the query embedding
  0.78, -- chose an appropriate threshold for your data
  10 -- choose the number of matches
);

至此,就完成了在supabase中进行向量检索的能力。

Supabase中的关键词检索

在Supabase上,除了使用向量检索外,还可以使用关键词检索。关键词检索会利用关键词和文档内容精确匹配来找到对应内容,非常适合查找特定术语。关键词检索通常通过PostgreSQL的全文搜索功能实现。

创建数据表

在supabase上创建一个表,并添加一个tsvector列以支持全文搜索。

sql 复制代码
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    content TEXT,
    fts TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);

创建索引

为了高效的进行关键词检索,提高全文检索的性能,我们可以创建一个GIN索引。

scss 复制代码
CREATE INDEX ON documents USING GIN(fts);

创建函数执行关键词搜索

sql 复制代码
CREATE FUNCTION kw_match_documents(query_text TEXT, match_count INT)
RETURNS TABLE (id BIGINT, content TEXT, metadata JSONB, similarity REAL)
AS $$
BEGIN
    RETURN QUERY EXECUTE
    FORMAT('SELECT id, content, metadata, ts_rank(to_tsvector(content), plainto_tsquery($1)) AS similarity
            FROM documents
            WHERE to_tsvector(content) @@ plainto_tsquery($1)
            ORDER BY similarity DESC
            LIMIT $2')
    USING query_text, match_count;
END;
$$ LANGUAGE PLPGSQL;

该函数使用plainto_tsquery将查询文本转换为tsquery,并结合ts_rank计算关键词匹配的相关性得分。

执行检索

sql 复制代码
SELECT * FROM kw_match_documents('your search query', 10);

Supabase中的混合检索

有时候单一的搜索方法可能不能完整的捕获我们的真实需求,这个时候可以考虑结合两种结合方式的优点,来实现混合检索。具体来说,首先,先分别执行每种搜索方法。关键词搜索是通过搜索内容中存在的特定单词或短语来进行的,它会产生自己的一组结果。语义搜索是通过理解搜索查询背后的上下文或含义,而不是所使用的特定单词来进行的,它会生成自己独特的结果。之后将它们合并成一个统一的列表。这是通过一个称为 "fusion" 的过程来实现的。fusion将两种搜索方法的结果收集起来,并根据特定的排名或评分系统将它们合并在一起。那么在supabase上应该如何实现呢?

创建数据表

sql 复制代码
create table documents (
  id bigint primary key generated always as identity,
  content text,
  fts tsvector generated always as (to_tsvector('english', content)) stored,
  embedding vector(512)
);

创建索引

这一步中我们将为数据表创建用于全文搜索的gen索引和用于语义检索的HNSW索引。

sql 复制代码
-- Create an index for the full-text search
create index on documents using gin(fts);

-- Create an index for the semantic vector search
create index on documents using hnsw (embedding vector_ip_ops);

创建混合检索方法

sql 复制代码
create or replace function hybrid_search(
  query_text text,
  query_embedding vector(512),
  match_count int,
  full_text_weight float = 1,
  semantic_weight float = 1,
  rrf_k int = 50
)
returns setof documents
language sql
as $$
with full_text as (
  select
    id,
    -- Note: ts_rank_cd is not indexable but will only rank matches of the where clause
    -- which shouldn't be too big
    row_number() over(order by ts_rank_cd(fts, websearch_to_tsquery(query_text)) desc) as rank_ix
  from
    documents
  where
    fts @@ websearch_to_tsquery(query_text)
  order by rank_ix
  limit least(match_count, 30) * 2
),
semantic as (
  select
    id,
    row_number() over (order by embedding <#> query_embedding) as rank_ix
  from
    documents
  order by rank_ix
  limit least(match_count, 30) * 2
)
select
  documents.*
from
  full_text
  full outer join semantic
    on full_text.id = semantic.id
  join documents
    on coalesce(full_text.id, semantic.id) = documents.id
order by
  coalesce(1.0 / (rrf_k + full_text.rank_ix), 0.0) * full_text_weight +
  coalesce(1.0 / (rrf_k + semantic.rank_ix), 0.0) * semantic_weight
  desc
limit
  least(match_count, 30)
$$;

执行检索

sql 复制代码
select
  *
from
  hybrid_search(
    'Italian recipes with tomato sauce', -- user query
    '[...]'::vector(512), -- embedding generated from user query
    10
  );

总结

至此,我们完成了在supabase中实现三种检索的方法的介绍。在这个过程中我们以具体代码的方式展示了应该如何去做,如果你的项目中也使用到了supabase并且也有这样的检索需求,那么本文应该会对你有所帮助。

参考文档

supabase.com/docs/guides...

相关推荐
xmyLydia16 分钟前
我做了一个代码生成器:Spring Boot + Angular 全自动构建
后端
supermfc23 分钟前
Docker方式离线部署OpenWebUI
后端·deepseek
橘猫云计算机设计1 小时前
基于django云平台的求职智能分析系统(源码+lw+部署文档+讲解),源码可白嫖!
数据库·spring boot·后端·python·django·毕业设计
码农小站1 小时前
MyBatis-Plus 表字段策略详解:@TableField(updateStrategy) 的配置与使用指南
java·后端
技术小丁1 小时前
使用PHP将PDF转换为图片(windows + PHP + ImageMagick)
后端
李憨憨1 小时前
深入探究MapStruct:高效Java Bean映射工具的全方位解析
java·后端
仰望星空的打工人1 小时前
若依改用EasyCaptcha验证码
后端
雷渊1 小时前
通俗易懂的来解释倒排索引
java·后端·面试
知其然亦知其所以然1 小时前
面试官狂喜!我用这 5 分钟讲清了 ThreadPoolExecutor 饱和策略,逆袭上岸
java·后端·面试
独立开阀者_FwtCoder1 小时前
分享 8 个 丰富的 Nextjs 模板网站
前端·javascript·后端