avatar

一条在知识海洋的咸鱼

这个家伙很懒,啥也没有留下😋

  • Linux
  • OCJP
  • Java核心技术卷
  • J2EE相关标准
  • 深入理解Java虚拟机
  • NIO与SOcket编程技术指南
  • Java多线程编程核心技术
  • Redis开发与运维
  • Spring Cloud Alibaba 微服务原理与实践
  • DevOps
  • Docker
  • MySQL必知必会
  • AI自学路线
  • Spring Boot 编程思想(核心篇)
  • 首页
主页 29 混合检索:结合语义召回和关键词精确匹配
文章

29 混合检索:结合语义召回和关键词精确匹配

发表于 最近 更新于 最近
作者 Administrator
35~45 分钟 阅读

混合检索:结合语义召回和关键词精确匹配

混合检索把向量检索与关键词检索结合起来。向量检索理解语义,关键词检索擅长错误码、接口路径、产品编号和专有名词,融合后可以减少单一路线的召回盲区。

[TOC]

1. 为什么需要混合检索

向量检索适合:

同义表达  
自然语言问题  
概念解释  
语义相近但字面不同的内容  

关键词检索适合:

ERR_10027  
/api/upload/max-size  
QWEN_MODEL  
订单号  
类名和函数名  
版本号  
人名和产品名  

例如:

用户:ERR_FILE_TOO_LARGE 是什么意思?  

向量模型可能把错误码看作普通符号,关键词检索可以精确命中包含该字符串的 chunk。

混合检索目标:

向量召回语义相关结果  
关键词召回精确匹配结果  
合并、去重、融合排序  
返回稳定候选集  

2. 整体流程

graph TD;  
 A["用户问题"] --> B["生成问题向量"];  
 A --> C["关键词和全文检索查询"];  
 B --> D["向量检索候选"];  
 C --> E["关键词检索候选"];  
 D --> F["统一候选结构"];  
 E --> F; F --> G["按 chunkId 去重"];  
 G --> H["融合排名"];  
 H --> I["取最终 TopK"];  

第一版参数示例:

vectorTopK = 20  
keywordTopK = 20  
finalTopK = 8  

后续如果增加 Rerank:

混合检索取20到50个候选  
Rerank后取5到8个上下文  

3. 统一候选结构

public record SearchCandidate(  
 String chunkId, String documentId, String content, double vectorScore, double keywordScore, Integer vectorRank, Integer keywordRank, String hitType, Map<String, Object> metadata) {  
}  

hitType:

VECTOR  
KEYWORD  
BOTH  

不要只保留最终分数。调试混合检索时必须知道:

候选来自哪一路  
原始排名是多少  
原始分数是多少  
融合后排名如何变化  

4. PostgreSQL 简单关键词检索

最简单的包含查询:

SELECT  
 c.id, c.document_id, c.contentFROM rag_chunk c  
JOIN rag_document d ON d.id = c.document_id  
WHERE d.knowledge_base_id = :knowledge_base_id  
 AND d.status = 'EMBEDDED' AND c.content ILIKE '%' || :keyword || '%'LIMIT :top_k;  

适合学习流程,但存在:

大数据量性能差  
无法良好分词和排序  
前导百分号难以使用普通B-tree索引  

可以启用 pg_trgm 改善模糊匹配:

CREATE EXTENSION IF NOT EXISTS pg_trgm;  
  
CREATE INDEX rag_chunk_content_trgm_idx  
ON rag_chunk  
USING gin (content gin_trgm_ops);  

仍需要根据中文、英文标识符和数据规模测试效果。


5. PostgreSQL 全文检索

可以增加搜索向量列:

ALTER TABLE rag_chunk  
ADD COLUMN search_vector tsvector;  

更新:

UPDATE rag_chunk  
SET search_vector = to_tsvector('simple', coalesce(title_path, '') || ' ' || content);  

索引:

CREATE INDEX rag_chunk_search_vector_gin_idx  
ON rag_chunk  
USING gin (search_vector);  

查询:

WITH query AS (  
 SELECT websearch_to_tsquery('simple', :question) AS value)  
SELECT  
 c.id, c.document_id, c.content, ts_rank_cd(c.search_vector, query.value) AS keyword_scoreFROM rag_chunk c  
CROSS JOIN query  
JOIN rag_document d ON d.id = c.document_id  
WHERE d.knowledge_base_id = :knowledge_base_id  
 AND d.status = 'EMBEDDED' AND c.search_vector @@ query.valueORDER BY keyword_score DESC  
LIMIT :top_k;  

websearch_to_tsquery 适合处理普通用户输入,并支持部分搜索语法。

中文全文检索需要额外评估分词方案。simple 配置不会自动提供成熟中文分词。

第一版可以组合:

错误码、接口路径、英文标识符:ILIKE或trigram  
自然语言:向量检索  

6. 关键词提取

第一版可以从问题中提取明显的精确标识符:

大写错误码:ERR_[A-Z0-9_]+  
接口路径:/api/...  
版本号:v1.2.3  
配置名:大写字母和下划线  
订单号:符合业务格式的数字或字母串  

不要让聊天模型随意改写精确标识符。

例如:

原问题:ERR_10027是什么?  
关键词:ERR_10027  

停用词、标点和大小写处理要可配置。


7. 为什么不能直接相加分数

向量分数可能是:

cosine similarity:0到1附近,越大越好  
cosine distance:0开始,越小越好  

全文检索分数可能是:

ts_rank_cd:没有与向量分数相同的量纲  

直接计算:

vectorScore + keywordScore  

可能让某一路长期压制另一路。

推荐先使用排名融合。


8. Reciprocal Rank Fusion

RRF 只关心各路排名,不要求原始分数同尺度。

公式:

RRF(d) = Σ 1 / (k + rank_i(d))  

其中:

d:候选chunk  
rank_i:候选在第i路检索中的排名,从1开始  
k:平滑常数,常见取60,也应通过评测调整  

示例:

chunk A:向量第1,关键词第4  
chunk B:向量第3,关键词第1  
A = 1/(60+1) + 1/(60+4)  
B = 1/(60+3) + 1/(60+1)  

Java 示例:

double rrfScore(Integer vectorRank, Integer keywordRank, int k) {  
 double score = 0.0; if (vectorRank != null) { score += 1.0 / (k + vectorRank); } if (keywordRank != null) { score += 1.0 / (k + keywordRank); } return score;}  

同一 chunk 两路都命中,通常会获得更高融合排名。


9. 归一化加权

另一种做法是先把分数归一化:

normalizedVectorScore  
normalizedKeywordScore  

再计算:

finalScore = 0.7 * normalizedVectorScore  
 + 0.3 * normalizedKeywordScore  

需要明确:

距离如何转换成相似度  
每一路如何归一化  
某一路没有结果时怎么办  
权重如何通过评测确定  

学习阶段优先 RRF,解释更简单,也不容易受分数量纲影响。


10. 去重

两路可能返回同一个 chunk。

必须按稳定 ID 去重:

chunkId  

不要只按内容文本去重,因为不同文档可以合法包含相同段落。

合并时保留:

vectorRank  
keywordRank  
vectorScore  
keywordScore  
hitType  

如果同一文档相邻 chunk 高度重叠,可以在最终上下文阶段再做相邻合并,不要在候选融合阶段过早删除。


11. 查询路由

第一版可以所有问题都执行两路检索。

后续可以根据问题特征路由:

包含错误码、路径、编号 -> 提高关键词权重  
自然语言解释 -> 提高向量权重  
短而精确的标识符 -> 关键词优先  
长问题或同义表达 -> 向量优先  

路由不能只有单一路径且没有回退。

如果分类错误,可能完全丢失正确候选。

稳妥方式:

保留两路检索  
根据问题类型调整候选数或融合权重  

12. 权限过滤

向量和关键词两路都必须使用同一授权范围。

不能:

向量检索带tenant过滤  
关键词检索却查询全库  

统一查询条件:

tenantId  
knowledgeBaseId  
documentStatus  
用户可访问范围  

融合前的每一条候选都必须已经授权。


13. 返回结构

调试接口可以返回:

{  
 "query": "ERR_FILE_TOO_LARGE是什么意思?",  
 "candidates": [ { "chunkId": "chunk_003", "hitType": "BOTH", "vectorRank": 4, "keywordRank": 1, "vectorScore": 0.81, "keywordScore": 0.72, "fusionScore": 0.032, "content": "ERR_FILE_TOO_LARGE表示文件超过大小限制。"  
 } ]}  

普通问答响应不必暴露全部内部排名,但日志和评测报告应保留这些信息。


14. 超时与降级

两路检索可能独立失败。

降级策略:

向量检索失败,关键词成功 -> 可降级到关键词结果  
关键词检索失败,向量成功 -> 可降级到向量结果  
两路都失败 -> 返回检索失败,不调用模型自由回答  

降级必须记录:

degraded=true  
failedChannel=VECTOR或KEYWORD  
错误原因  

不能悄悄降级,否则团队会误以为混合检索一直正常。


15. 评测

准备不同问题类型:

同义表达  
错误码  
接口路径  
产品编号  
自然语言事实  
中英文混合术语  

分别比较:

仅向量检索 Recall@K仅关键词检索 Recall@K混合检索 Recall@K最终来源命中率  
查询P95延迟  

不要只看最终答案。必须知道提升来自哪一路。


16. 可观测性

记录:

vectorCandidateCount  
keywordCandidateCount  
overlapCount  
vectorOnlyCount  
keywordOnlyCount  
fusionTopK  
两路耗时  
降级状态  

异常信号:

重合率长期为0  
关键词结果从不进入最终TopK  
向量结果总被关键词压制  
某一路候选长期为空  

这些通常意味着分词、索引、归一化或融合配置存在问题。


17. 常见问题

17.1 混合检索一定更好吗

不一定。

融合策略错误会把不相关关键词结果排到前面,所以必须评测。

17.2 第一版必须使用 Elasticsearch 吗

不必。

可以先用 PostgreSQL 的向量、trigram 和全文检索理解流程。数据规模和搜索需求增加后再评估 Elasticsearch/OpenSearch。

17.3 中文全文检索怎么处理

需要专门的中文分词或外部搜索引擎评估。不能假设 PostgreSQL 默认配置已经具备理想中文分词效果。

17.4 混合检索能替代 Rerank 吗

不能完全替代。

混合检索提高召回覆盖,Rerank 对候选做更精细的 query-passage 相关性排序。

17.5 RRF 的 k 固定为60吗

不是。

60 是常见起点,最终应由评测决定。


18. 练习清单

实现向量和关键词两路检索  
提取错误码和接口路径关键词  
为关键词检索建立合适索引  
统一两路候选结构  
按chunkId去重  
实现RRF融合  
记录VECTOR、KEYWORD、BOTH命中类型  
两路使用相同权限过滤  
实现单路失败降级  
用固定问题集比较三种检索效果  

19. 小结

混合检索链路:

query  
 -> vector retrieval -> keyword retrieval -> merge -> deduplicate -> rank fusion -> final candidates  

本节最重要的结论:

向量检索解决“意思相近”,关键词检索解决“字面必须命中”。混合检索的关键不只是两路查询,而是可解释、可评测的融合排序。

20. 参考资料

  • PostgreSQL Text Search
  • PostgreSQL Text Search Functions
  • Spring AI and Elasticsearch
  • pgvector
AI自学路线
许可协议: 
分享

相关文章

7月 8, 2026

29 混合检索:结合语义召回和关键词精确匹配

混合检索:结合语义召回和关键词精确匹配 混合检索把向量检索与关键词检索结合起来

6月 18, 2026

28 RAG 第一版交付:从功能拼接到可演示系统

RAG 第一版交付:从功能拼接到可演示系统 RAG 第一版交付的目标不是加入所有高级能力,而是把

6月 16, 2026

27 对话历史:让多轮 RAG 正确理解追问

对话历史:让多轮 RAG 正确理解追问 对话历史的核心不是把所有聊天记录不断塞给模型,而是保存完&#

下一篇

上一篇

28 RAG 第一版交付:从功能拼接到可演示系统

最近更新

  • 29 混合检索:结合语义召回和关键词精确匹配
  • 28 RAG 第一版交付:从功能拼接到可演示系统
  • 27 对话历史:让多轮 RAG 正确理解追问
  • 26 权限隔离:让无权内容无法进入 RAG 上下文
  • 25 引用来源:让 RAG 答案可以核对和追溯

热门标签

java基础 微服务 maven Spring Tomcat DDD Linux Linux基础 SQL基础 数据结构算法

目录

©2026 一条在知识海洋的咸鱼. 保留部分权利。

使用 Halo 主题 Chirpy