关注【云引】——云原生、数据治理、人工智能应用技术宅灵感补给池,每天 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的应用快速构建能力,这样的组合是值得一用的搜索引擎解决方案。