首页/新闻资讯/正文详情

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

发布时间:2026/9/27 21:31:06 来源:云帆数科 栏目:资讯中心
Java搭RAG实战:混合检索+重排序:完整源码+逐行设计决策
先说结论纯向量检索的召回率只有40%加上关键词检索和Rerank后命中提升到88%。这是4个Java类完整源码踩坑记录。一、配置层HybridRetrievalPropertiesConfigurationProperties(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而不是classrecord的自动生成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 ListRankedDocument search(String query, int topK) { ListString 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; ListRankedDocument 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字词太短容易误命中权重低合理。三、重排序RerankServicepublic ListRerankedDocument rerank(String query, ListDocument 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 { // 构建请求体 ListString 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(); HttpResponseString 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用错了会报错排查半天才定位到。降级逻辑三级降级配置层禁用 → 返回原始顺序API返回非200 → 返回原始顺序异常捕获 → 返回原始顺序Rerank挂了不影响主流程这个设计保证了混合检索的稳定性。四、融合层HybridRetrievalServicepublic ListDocument 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. 双路召回向量检索 关键词检索 ListDocument vectorDocs vectorSearch(query, topK * 2); ListRankedDocument keywordDocs keywordSearch.search(query, hybrid.keywordTopK()); // 2. RRF融合 ListDocument fused rrfFusion(vectorDocs, keywordDocs, topK * 2); // 3. Rerank重排序可选 if (hybrid.rerankEnabled()) { ListRerankedDocument 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 ListDocument rrfFusion( ListDocument vectorDocs, ListRankedDocument keywordDocs, int maxResults) { MapString, Double rrfScores new HashMap(); MapString, 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得分降序 ListMap.EntryString, Double sorted new ArrayList(rrfScores.entrySet()); sorted.sort((a, b) - Double.compare(b.getValue(), a.getValue())); // 截断到maxResults ListDocument 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/(krank)k60是经验值。k越大各路检索结果的排名差异对最终得分的影响越小。这让向量和关键词两路的结果能更平滑地融合而不是被单一检索结果主导。五、实测数据场景纯向量v1关键词命中v2关键词命中v1 Rerankv2 Rerank中文语义介绍Dream-SaaS0.26820200.5240.879专有名词code-review-agent0.43420200.5240.860中文关键词向量数据库选型0.5380200.5170.514模糊语义怎么让AI基于知识库回答0.4460200.4600.465结论N-gram分词后中文关键词命中从0提升到20Rerank后相关性从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%免费】

相关推荐

植物大战僵尸杨桃阵型攻防解析:僵尸生存机制与战术破解
植物大战僵尸杨桃阵型攻防解析:僵尸生存机制与战术破解

这次我们来分析一个有趣的植物大战僵尸战术问题:在究极杨桃剑仙组成的"尸愁之路"防守阵型中,究竟哪些僵尸能够存活下来?这个问题的核心在于理解杨桃的攻击机制和不同僵尸的防御特性。究极杨桃剑仙是玩家对强化版杨桃的戏称&#xf… · 2026/9/2 15:55:18

CW32饭盒派开发板RTC模块开发指南
CW32饭盒派开发板RTC模块开发指南

1. CW32饭盒派开发板与RTC基础解析CW32饭盒派开发板是一款面向嵌入式开发者的多功能评估平台,搭载了武汉芯源半导体(CW)的32位微控制器。这款开发板因其紧凑的外形设计和丰富的功能接口被开发者亲切地称为"饭盒派",特别适合物联网终端设备、智… · 2026/9/20 12:50:04

Ubuntu24.04 LTS:老旧设备变身高效家庭服务器的终极指南
Ubuntu24.04 LTS:老旧设备变身高效家庭服务器的终极指南

1. 老旧设备焕新:为什么选择Ubuntu24.04作为家庭服务器系统? 十年前的老笔记本在抽屉里吃灰?淘汰的平板电脑只能当电子相框?这些被时代淘汰的硬件其实藏着惊人的潜力。我最近用一台2014年的联想Yoga平板(Intel Atom Z3… · 2026/9/19 4:43:25

将 Sphinx 文档嵌入 Web 应用:Sphinx Web Support 集成实战指南
将 Sphinx 文档嵌入 Web 应用:Sphinx Web Support 集成实战指南

文档开发工具 【免费下载链接】sphinx The Sphinx documentation generator 项目地址: https://gitcode.com/gh_mirrors/sp/sphinx 点击查看 免费下载 Sphinx 不仅能把 reStructuredText 源码编译成静态站点,还通过 Web Support 提供了一套 Python API&… · 2026/9/27 21:31:03

Claude Code 接入阿里千问 Qwen3:环境变量与 settings.json 配置实战
Claude Code 接入阿里千问 Qwen3:环境变量与 settings.json 配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 21:31:03

Python爬虫项目如何实现代理IP自动轮换?
Python爬虫项目如何实现代理IP自动轮换?

用 Python 写爬虫,最令人头疼的问题之一就是:IP 被封,数据采集中断。你可能设置了headers、加了sleep、甚至换了UA,结果没几分钟还是被网站“识破”。 这时候你就需要引入代理池——一种通过自动切换代理 IP 进行请求的策略&… · 2026/9/27 21:31:03

Windows 上把 Claude Code 接到 DeepSeek V4:TaoToken 统一 Key 配置与验证手把手
Windows 上把 Claude Code 接到 DeepSeek V4:TaoToken 统一 Key 配置与验证手把手

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 21:31:03

WPScan 插件版本识别实战:基于 Change Log 文件与 BodyPattern 动态查找器的版本检测
WPScan 插件版本识别实战:基于 Change Log 文件与 BodyPattern 动态查找器的版本检测

网络安全漏洞扫描渗透测试应用安全CLI 【免费下载链接】wpscan WPScan WordPress security scanner. Written for security professionals and blog maintainers to test the security of their WordPress websites. Contact us via contactwpscan.com 项目地址: ht… · 2026/9/27 21:31:03

Windows下Codex个人APIKey和代理配置:TaoToken统一Key接入settings.json骨架
Windows下Codex个人APIKey和代理配置:TaoToken统一Key接入settings.json骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 21:30:57

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码