首页
学习
活动
专区
圈层
工具
发布

Supabase构建混合搜索应用:PostgreSQL向量搜索+全文搜索的完美融合

关注【云引】——云原生、数据治理、人工智能应用技术宅灵感补给池,每天 3 分钟,沉淀干货,极客开脑洞!

浓缩版本

本文聊聊混合搜索相关内容, 以及怎么用supabase快速实现一个混合搜索应用。

混合搜索时传统精确搜索和语义搜索的“既要又要还要”升级版,巧妙地结合了两种搜索方法的优势,通过倒排融合算法,帮助人们发现最合理检索结果。

混合搜索

混合搜索将全文搜索(按关键词搜索)与语义搜索(按含义搜索)相结合,把字面匹配和意思相近的结果合并一起,综合评分返回更加合理的搜索结果。

融合过程

关键词搜索分支

语义搜索分支

用户输入查询

生成查询向量

预处理查询文本

语义搜索

关键词搜索

语义搜索结果

关键词搜索结果

融合算法 RRF

计算RRF分数

按分数排序

返回最终结果

向量相似度计算

距离排序

文本解析

词素匹配

相关性排序

收集所有候选结果

计算每个结果的RRF分数

加权合并

倒排融合 (RRF) 算法详解

倒排融合算法的核心公式:

RRF分数 = (1/(k + rank₁)) × weight₁ + (1/(k + rank₂)) × weight₂

其中:

•rank₁: 在关键词搜索中的排名

•rank₂: 在语义搜索中的排名

•k: 平滑常数(通常为50-60)

•weight₁/₂: 各搜索方法的权重

示例计算

假设一个文档在两种搜索中的排名:

最终排序:Doc A (0.0388) > Doc B (0.0383) > Doc C (0.0379)

混合搜索场景

想象以下美食搜索"意大利番茄酱面食"的场景。传统的关键词搜索只会找出所有明确提到"意大利"、"番茄酱"和"面食"的食谱,但会错过那些使用"玛丽娜拉酱"或"通心粉"等同义词的经典菜谱。纯语义搜索能理解你的意图,找到各种意大利面相关菜谱,却可能推荐一些相近的墨西哥沙拉酱食谱。

混合搜索,它巧妙地结合了两种搜索方法的优势,关键词搜索确保找到精确匹配结果语义搜索则能发现意义相近的内容。通过倒排融合算法,提供以下能力:

优先展示最相关的结果:既包含关键词精确匹配结果,也包括语义相似内容

避免遗漏重要信息:不会错过真正有语义价值的内容

提高搜索质量:在购物、文档检索、内容发现等场景中提供更全面准确的搜索结果

平衡精确性和覆盖面:既保证找到用户想要的特定内容,又能发现用户可能感兴趣的相关内容

混合搜索好比是经验丰富的图书管理员,能根据书名快速找到特定书籍,又能根据内容主题推荐相关读物,帮助人们发现最适合他们的信息。

Supabase构建混合搜索

Supabase完整应用调用过程

RRF融合语义搜索关键词搜索PostgreSQLSupabaseOpenAI APIEdge Function用户

RRF融合

语义搜索

关键词搜索

PostgreSQL

Supabase

OpenAI API

Edge Function

用户

参数传递:• query_text: "Italian recipes with tomato sauce"• query_embedding: [向量数据]• match_count: 10• full_text_weight: 1• semantic_weight: 1• rrf_k: 50计算公式:score = 1/(k + rank₁) × weight₁ + 1/(k + rank₂) × weight₂发送查询 "Italian recipes with tomato sauce"

请求生成embedding向量

返回query_embedding向量

调用hybrid_search函数

执行混合搜索

关键词搜索分支

语义搜索分支

返回关键词结果+排名

返回语义结果+排名

执行RRF融合算法

返回融合排序结果

返回最终文档列表

返回搜索结果

显示搜索结果

这里少不了Supabase的Edge Function,首先是对用户输入调用Embedding向量化,接着RPC调用hybrid_search函数获取结果。

• query_text:用户输入的原始的查询文本

• query_embedding:从Embedding Model获取的向量化数据

import { createClient } from'npm:@supabase/supabase-js@2'

importOpenAIfrom'npm:openai'

const supabaseUrl = Deno.env.get('SUPABASE_URL')!

const supabaseServiceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!

const openaiApiKey = Deno.env.get('OPENAI_API_KEY')!

Deno.serve(async (req) => {

// Grab the user's query from the JSON payload

const { query } = await req.json()

// Instantiate OpenAI client

const openai = newOpenAI({ apiKey: openaiApiKey })

// Generate a one-time embedding for the user's query

const embeddingResponse = await openai.embeddings.create({

  model: 'text-embedding-3-large',

  input: query,

  dimensions: 512,

})

const [{ embedding }] = embeddingResponse.data

// Instantiate the Supabase client

// (replace service role key with user's JWT if using Supabase auth and RLS)

const supabase = createClient(supabaseUrl, supabaseServiceRoleKey)

// Call hybrid_search Postgres function via RPC

const { data: documents } = await supabase.rpc('hybrid_search', {

  query_text: query,

  query_embedding: embedding,

  match_count: 10,

})

returnnewResponse(JSON.stringify(documents), {

  headers: { 'Content-Type': 'application/json' },

})

})数据库表设计

混合搜索表结构如下所示,分别定义了关键词搜索的向量和语义搜索的向量,并构建索引。

-- 表结构设计

CREATE TABLE documents (

id bigint PRIMARY KEY,

content text,                                    -- 原始内容

fts tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,  -- 关键词搜索向量

embedding vector(512)                           -- 语义搜索向量

);

-- 索引设计

CREATE INDEX ON documents USING gin(fts);         -- 关键词搜索索引

CREATE INDEX ON documents USING hnsw (embedding vector_ip_ops);  -- 语义搜索索引hybrid_search函数设计

hybrid_search函数实现混合搜索,hybrid_search大体结构如图所示:

调用流程

用户查询

生成embedding

调用hybrid_search

返回结果

hybrid_search函数

输入参数

关键词搜索CTE

语义搜索CTE

fts @@ websearch_to_tsquery

embedding <#> query_embedding

关键词结果 + 排名

语义结果 + 排名

FULL OUTER JOIN

计算RRF分数

ORDER BY分数

LIMIT返回

这里CTE是用with queries结构打包的临时结果集,对应函数定义中的full_text和semantic

1. hybrid_search函数定义

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

languagesql

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(orderby ts_rank_cd(fts, websearch_to_tsquery(query_text)) desc) as rank_ix

from

documents

where

fts @@ websearch_to_tsquery(query_text)

orderby rank_ix

limit least(match_count, 30) *2

),

**semantic**as (

select

id,

row_number() over (orderby embedding <#> query_embedding) as rank_ix

from

documents

orderby rank_ix

limit least(match_count, 30) *2

)

select

documents.*

from

full_text

fullouterjoin semantic

on full_text.id = [semantic.id](http://semantic.id/)

join documents

oncoalesce(full_text.id, [semantic.id](http://semantic.id/)) = [documents.id](http://documents.id/)

orderby

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)

$$;2. 权重平衡

-- 可以调整关键词和语义搜索的权重

SELECT * FROM hybrid_search(

query_text,

query_embedding,

match_count,

full_text_weight => 1.5,  -- 关键词搜索权重

semantic_weight => 1.0    -- 语义搜索权重

);

3. 性能考量

• 预处理:原始数据经过预处理,生成tsvector列存储。

• “分而治之”的索引策略:GIN是全文索引使用的高效的复合值索引,HNSW是向量索引(在之前的文章中曾介绍过)。

• 可调权重的结果:在每个分支中结合搜索权重配置,综合返回限定数量的结果。

结论

混合搜索保证了精确匹配的准确性,兼顾了语义理解的相关性,Postgresql提供的全文搜索和向量搜索能力,Supabase的应用快速构建能力,这样的组合是值得一用的搜索引擎解决方案。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OQqm3ulzC3X5KHssGjnzL8Xg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券