当前位置: 首页 > news >正文

Java搭RAG实战:混合检索+重排序:完整源码+逐行设计决策


先说结论

纯向量检索的召回率只有40%,加上关键词检索和Rerank后命中提升到88%。这是4个Java类完整源码+踩坑记录。


一、配置层:HybridRetrievalProperties

@ConfigurationProperties(prefix = "dream-saaa-s.rag.hybrid")public record HybridRetrievalProperties( @DefaultValue("true") boolean enabled, @DefaultValue("0.7") double vectorWeight, @DefaultValue("0.3") double keywordWeight, @DefaultValue("60") int rrfK, @DefaultValue("20") int keywordTopK, @DefaultValue("true") boolean rerankEnabled, @DefaultValue("qwen3-rerank") String rerankModel, @DefaultValue("5") int rerankTopN) {}

设计决策:为什么用record而不是class?

record的自动生成equals()/hashCode()/toString()让我省了15行模板代码。更重要的是,record的不可变性天然适合配置对象——配置在启动时读取后就不该被修改。

坑1:百炼模型名

rerankModel默认值我填的是qwen3-rerank,不是bge-reranker。原因是百炼的bge-reranker模型返回"Model not exist",这破坑折腾了我两天。实际可用的是qwen3-rerank,别填bge-reranker。

设计决策:为什么权重是0.7/0.3?

这是调出来的初始值。向量检索负责语义匹配,权重高;关键词负责精确匹配,权重低。后面测试数据会验证这个配比是否合理。


二、关键词检索:KeywordSearchService核心方法

public List<RankedDocument> search(String query, int topK) { List<String> keywords = extractKeywords(query); if (keywords.isEmpty()) { log.warn("关键词检索:query拆词后为空 query={}", query); return Collections.emptyList(); } // 动态构建SQL的WHERE子句 String whereClause = keywords.stream() .map(kw -> "content ILIKE ?") .collect(Collectors.joining(" OR ")); // 构建LIKE参数列表 Object[] params = keywords.stream() .map(kw -> "%" + kw + "%") .toArray(); // SQL: SELECT ... FROM rag_documents WHERE content ILIKE ? OR content ILIKE ? ... String sql = "SELECT id, content, metadata FROM rag_documents WHERE " + whereClause + " LIMIT ?"; // params + topK合并成完整参数数组 Object[] fullParams = new Object[params.length + 1]; System.arraycopy(params, 0, fullParams, 0, params.length); fullParams[params.length] = topK * 3; List<RankedDocument> results = jdbc.query(sql, fullParams, (rs, rowNum) -> { String content = rs.getString("content"); String lowerContent = content.toLowerCase(); int hitCount = 0; double weightedScore = 0.0; for (String kw : keywords) { if (lowerContent.contains(kw.toLowerCase())) { hitCount++; // 加权逻辑:长词权重更高 weightedScore += kw.length() >= 4 ? 2.0 : kw.length() == 3 ? 1.5 : 1.0; } } // 归一化:实际得分 / 理论满分 double maxPossible = keywords.stream() .mapToDouble(kw -> kw.length() >= 4 ? 2.0 : kw.length() == 3 ? 1.5 : 1.0) .sum(); double score = maxPossible > 0 ? weightedScore / maxPossible : 0.0; returnnew RankedDocument(id, content, metadataJson, score, hitCount); }); // 按得分降序排列 results.sort((a, b) -> Double.compare(b.score, a.score)); return results.size() > topK ? results.subList(0, topK) : results;}

设计决策:为什么LIMIT topK * 3

关键词命中的20条不一定都是高质量的,我多召回一些给RRF融合用。融合后还会再截断到topK。

坑2:中文整句ILIKE完全失效

我测试过content ILIKE '%向量数据库选型%',中文整句匹配返回0条。但拆成['向量', '数据库', '选型', '向量数据库', '数据库选型']后,20条全部命中。结论:PostgreSQL的ILIKE对中文整句支持有bug,必须先分词。

设计决策:加权2.0/1.5/1.0怎么来的?

长度≥4的词给2.0分(专业术语通常4字以上),3字词给1.5分,2字词给1.0分。这是业务经验沉淀,不是拍脑袋。2字词太短容易误命中,权重低合理。


三、重排序:RerankService

public List<RerankedDocument> rerank(String query, List<Document> documents, int topN) { // 降级:未启用则返回原始顺序 if (!properties.rerankEnabled()) { return documents.stream() .map(d -> new RerankedDocument(d, 1.0)) .limit(topN) .toList(); } if (documents.isEmpty()) { return Collections.emptyList(); } try { // 构建请求体 List<String> docTexts = documents.stream() .map(d -> truncate(d.getText(), 500)) // 截断到500字符 .toList(); String requestBody = MAPPER.writeValueAsString(Map.of( "model", properties.rerankModel(), "query", query, "documents", docTexts, "top_n", Math.min(topN, docTexts.size()) )); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(RERANK_URL)) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .timeout(Duration.ofSeconds(30)) .build(); HttpResponse<String> response = httpClient.send( request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { log.error("Rerank API 失败 status={} body={}", response.statusCode(), response.body()); // 降级:返回原始顺序 return documents.stream() .map(d -> new RerankedDocument(d, 1.0)) .limit(topN) .toList(); } return parseRerankResponse(response.body(), documents, topN); } catch (Exception e) { log.error("Rerank 异常,降级返回原始顺序", e); return documents.stream() .map(d -> new RerankedDocument(d, 1.0)) .limit(topN) .toList(); }}

设计决策:为什么截断到500字符?

百炼rerank API有输入长度限制,500是实测安全值。超长文本传进去要么被截断要么报错,提前截断更可控。

坑3:接口路径不是dashscope原生

接口路径是/compatible-api/v1/reranks,不是/rerank,用错了会报错,排查半天才定位到。

降级逻辑:三级降级

  1. 配置层禁用 → 返回原始顺序
  2. API返回非200 → 返回原始顺序
  3. 异常捕获 → 返回原始顺序

Rerank挂了不影响主流程,这个设计保证了混合检索的稳定性。


四、融合层:HybridRetrievalService

public List<Document> hybridRetrieve(String query, Integer topKOverride) { if (!StringUtils.hasText(query)) { return Collections.emptyList(); } int topK = topKOverride != null && topKOverride > 0 ? topKOverride : retrieval.topK(); // 关闭混合检索时退化为纯向量 if (!hybrid.enabled()) { return vectorSearch(query, topK); } // 1. 双路召回:向量检索 + 关键词检索 List<Document> vectorDocs = vectorSearch(query, topK * 2); List<RankedDocument> keywordDocs = keywordSearch.search(query, hybrid.keywordTopK()); // 2. RRF融合 List<Document> fused = rrfFusion(vectorDocs, keywordDocs, topK * 2); // 3. Rerank重排序(可选) if (hybrid.rerankEnabled()) { List<RerankedDocument> reranked = rerankService.rerank( query, fused, hybrid.rerankTopN()); return reranked.stream().map(RerankedDocument::document).toList(); } return fused.size() > topK ? fused.subList(0, topK) : fused;}

RRF融合核心逻辑:

private List<Document> rrfFusion( List<Document> vectorDocs, List<RankedDocument> keywordDocs, int maxResults) { Map<String, Double> rrfScores = new HashMap<>(); Map<String, Document> docMap = new HashMap<>(); int k = hybrid.rrfK(); // 默认60 // 向量检索结果参与RRF打分 for (int i = 0; i < vectorDocs.size(); i++) { Document doc = vectorDocs.get(i); String id = doc.getId(); int rank = i + 1; rrfScores.merge(id, hybrid.vectorWeight() / (k + rank), Double::sum); docMap.putIfAbsent(id, doc); } // 关键词检索结果参与RRF打分 for (int i = 0; i < keywordDocs.size(); i++) { RankedDocument rd = keywordDocs.get(i); String id = rd.id(); int rank = i + 1; rrfScores.merge(id, hybrid.keywordWeight() / (k + rank), Double::sum); docMap.putIfAbsent(id, buildDocumentFromRanked(rd)); } // 按RRF得分降序 List<Map.Entry<String, Double>> sorted = new ArrayList<>(rrfScores.entrySet()); sorted.sort((a, b) -> Double.compare(b.getValue(), a.getValue())); // 截断到maxResults List<Document> result = new ArrayList<>(); for (int i = 0; i < Math.min(sorted.size(), maxResults); i++) { result.add(docMap.get(sorted.get(i).getKey())); } return result;}

设计决策:为什么用merge而不是put

两个检索结果可能有重叠的文档。比如"向量数据库选型"这个query,向量检索和关键词检索可能召回同一篇文档。merge保证同一条文档的得分累加,而不是被覆盖。

RRF公式:1/(k+rank)

k=60是经验值。k越大,各路检索结果的排名差异对最终得分的影响越小。这让向量和关键词两路的结果能更平滑地融合,而不是被单一检索结果主导。


五、实测数据

场景纯向量v1关键词命中v2关键词命中v1 Rerankv2 Rerank
中文语义"介绍Dream-SaaS"0.26820200.5240.879
专有名词"code-review-agent"0.43420200.5240.860
中文关键词"向量数据库选型"0.5380200.5170.514
模糊语义"怎么让AI基于知识库回答"0.4460200.4600.465

结论:N-gram分词后,中文关键词命中从0提升到20,Rerank后相关性从0.514提升到0.879。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

http://www.jsqmd.com/news/1209380/

相关文章:

  • 科源制药医药主业筑基,脑机接口与具身智能打开成长空间
  • AI产品用户体验优化:从SOTA模型到不被轻视的体验设计
  • 干货必藏!重庆大足转院护送救护车租赁,长途平稳护送安全到家 - 资讯速览
  • 聚焦民生法律服务 助力家事权益保障 - 产业观察报
  • 转轴故障诊断与修复:从结构原理到工程实践
  • PVZ3E冷沙拉植物深度评测:范围控制与战术搭配全解析
  • 2026论文终极内耗!被Word/WPS坑哭的无数夜晚|格式崩图乱、多软件来回切,一键根治
  • AI智能体与高德地图MCP Server的行程规划实践
  • 2026年7月最新资阳防水补漏权威指南:卫生间/屋面/外墙/地下室正规施工+透明报价+避坑全攻略 - 吉林同城获客
  • 告别协议零和内卷:TrueDAO 搭建全行业共享 AI 自治金融底座,重构链上价值底层逻辑
  • SMT工艺中锡膏与红胶的协同应用与优化
  • T3 Code:统一管理多AI编程助手的Web GUI解决方案
  • SpeechAnalyzer API性能对比:iOS语音识别技术迁移与优化实践
  • 本月最新!重庆江津长途转运非急救救护车选择,正规直营车队实力盘点 - 资讯速览
  • 香港分销商城软件APP开发
  • 年中更新:上海静安非急救长途转运救护车租赁,全车型按需调配上门接 - 资讯速览
  • WSL2安装问题排查与解决方案详解
  • Linux 进程管理:状态、优先级与调度核心解析
  • 大模型微调实战:原理、技巧与优化方法
  • OCR技术原理、工具选型与工程实践指南
  • Temu商品图翻译API自动化实现方案
  • 实战 WorkBuddy+反爬+爬虫固化 Skill,6大能力搞定浏览器自动化
  • 数据分析项目去哪里找:2026年6大类真实业务项目资源整理 (1) - 资讯速览
  • SolidWorks Flow Simulation项目克隆:高效变体分析与仿真工作流优化
  • 【AI编程】 程序员 AI 提示词工程实战-CRISPE 与 CO-STAR 框架的全场景指南
  • 应届生没有实习经历,怎么证明自己有能力
  • 广州番禺区搬家避坑全指南:破解临时加价套路,现场维权实操方法 - 厚道搬家
  • 2026 年南京漏水不用慌:6 大高频场景专属方案,徽顺虹针对性治理全解析 - 徽顺虹
  • 2026 广州天河区搬家公司 10 公里基础收费标准 街道豪宅写字楼搬迁报价明细 - 厚道搬家
  • 2026毕业论文新手必看!从零写作全程避坑指南|零基础也能一次性定稿通关