在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...

相关推荐
倔强青铜三35 分钟前
苦练Python第4天:Python变量与数据类型入门
前端·后端·python
倔强青铜三43 分钟前
苦练Python第3天:Hello, World! + input()
前端·后端·python
倔强青铜三1 小时前
苦练Python第2天:安装 Python 与设置环境
前端·后端·python
Kookoos1 小时前
ABP VNext + .NET Minimal API:极简微服务快速开发
后端·微服务·架构·.net·abp vnext
倔强青铜三1 小时前
苦练Python第1天:为何要在2025年学习Python
前端·后端·python
LjQ20402 小时前
Java的一课一得
java·开发语言·后端·web
求知摆渡3 小时前
共享代码不是共享风险——公共库解耦的三种进化路径
java·后端·架构
brzhang3 小时前
前端死在了 Python 朋友的嘴里?他用 Python 写了个交互式数据看板,着实秀了我一把,没碰一行 JavaScript
前端·后端·架构
该用户已不存在3 小时前
不知道这些工具,难怪的你的Python开发那么慢丨Python 开发必备的6大工具
前端·后端·python
Xy9104 小时前
开发者视角:App Trace 一键拉起(Deep Linking)技术详解
java·前端·后端