企业级开源智能体系统 RAG优化升级
首先要申明 这不仅是一个rag系统,是一个智能agent系统可以搭载rag。rag系统是其中一个重要的环节。这个版本做的迭代是rag部分的优化。
rag系统的优化策略分为三步走,事前(如何分块) 事中(如何检索刀有效块) 事后(召回优化)。
优化思路:
| 技术 | 原理 |
|---|---|
| Query2Doc | 利用 LLM 生成伪文档,将查询与伪文档拼接后检索,弥补查询信息不足 |
| HyDE(假设文档嵌入) | 生成假设性答案并嵌入,可选与原始查询嵌入取平均,用向量检索 |
| 子问题拆解 | 将复杂问题分解为多个子问题,分别检索后回答 |
| 查询改写 | 生成查询的转述版本,增加领域关键词提升召回率 |
| Step-Back 提示 | 通过少样本示例,将具体问题抽象为更一般性的问题,更容易匹配到相关文档 |
| Multi-Query(多查询生成) | LLM 从不同角度生成多个查询变体,分别检索后取并集去重,提升召回率 |
| RAG Fusion(递归融合检索) | Multi-Query + RRF 倒数排名融合,多查询共识的文档排名更高,提升排序质量 |
| PRF(伪相关反馈) | 首轮检索 Top-K 结果中提取关键词扩展查询,再检索,数据驱动的自举策略 |
| 语义扩展(Embedding Expansion) | 在词向量空间中找查询关键词的近义词/相关词扩展查询,无需 LLM,速度快 |
| 上下文查询重构 | 多轮对话中补全指代词和省略信息,生成独立可检索的完整查询 |
| RaFe(排序反馈改写) | 小模型改写查询→检索→评估排序质量→反馈优化,形成可学习的改写闭环 |
| 技术 | 原理 |
|---|---|
| Auto-Merging(自动合并检索) | 多层树状索引,子块命中超阈值自动合并为父块,避免零散返回 |
| Multi-Vector / ColBERT(多向量检索) | 一个文档对应多个 token 级向量,迟交互机制做词级精细匹配 |
| 知识图谱索引 | 提取实体和关系建图,通过路径遍历支持多跳推理检索 |
| RAPTOR(层级递归索引) | 递归聚类+摘要构建多层树,检索时适配不同抽象层级 |
| 元数据过滤索引 | 向量索引附加结构化元数据,先硬过滤再向量检索,精准缩小范围 |
| SPLADE(稀疏学习索引) | 学习型稀疏向量,兼得 BM25 关键词匹配 + 语义理解能力 |
以下是鄙人的一家之言,仅供参考!!!
通用型的系统,做文档上传->分块->自然语言问题->查询数据给模型->返回结果。
工程上优化的再好普适性的也就是60分往上,很难说做到所有场景都是90分。
最好的rag问答,是数据清洗完之后再分块,然后将通用性的问答沉淀到FAQ,这个才是首选,技术角度提升效果有限。
不要一个机器人挂不同业务的文档库,可以做一个简单的意图识别,不同的业务甚至同一个业务不同的模块都可以拆分为一个机器人,通过意图识别去路由对应的机器人进行问答。
这个是爱码士认为的最优解。以上优化大部分是非技术角度的,今天给大家带来技术角度的优化。是事中、事后的优化,事前部分的优化下个版本迭代,给大家提供一种思路,花钱能解决的事情千万别写代码。
此次的整体流程升级
用户提问后,系统经过4 个阶段从知识库中找到最相关的文档片段:
BM25 + 向量双路召回 → RRF 融合排名 → CrossEncoder 重排 → 置信度自适应加权出最终结果1️⃣双路并行检索(BM25 + 向量语义检索)
想象你要在图书馆找一本书,有两种查找方式:
| 检索方式 | 生活比喻 | 技术原理 | 优势 | 劣势 |
|---|---|---|---|---|
| BM25 全文检索 | 按关键词查字典 比如搜"苹果",找所有包含"苹果"的文档 | 统计查询词在文档中出现的频率 (词频越高越相关) | ✅ 精准匹配关键词 ✅ 速度快 | ❌ 不懂语义 ❌ “苹果手机"搜不到"iPhone” |
| 向量语义检索 | 按意思找朋友 比如你说"想吃水果",朋友递给你苹果 | 把文字转成向量(数字数组) 计算相似度(余弦相似度) | ✅ 理解语义 ✅ “苹果手机"能匹配"iPhone” | ❌ 可能忽略关键词 ❌ 计算慢 |
2️⃣RRF 智能排序融合(Reciprocal Rank Fusion)
问题:文档 A 在 BM25 排第 1,在向量检索排第 2,它应该是总榜第几名? 使用RRF算法的目的是将两个榜单合并
3️⃣CrossEncoder 高精度重排
什么是 CrossEncoder?
想象你是阅卷老师,有两种批改方式:
| 模型类型 | 比喻 | 工作原理 | 精度 | 速度 |
|---|---|---|---|---|
| BiEncoder(向量检索用) | 快速初筛 给每份试卷单独打分 | 分别计算 query 和文档的向量 然后算相似度 | ⭐⭐⭐ | 🚀 快 |
| CrossEncoder(重排用) | 精细批改 逐字逐句对比 | 把 query 和文档拼在一起 用注意力机制深度交互 | ⭐⭐⭐⭐⭐ | 🐢 慢 |
BiEncoder: query → [向量] ↘ 余弦相似度 → 0.85 文档 → [向量] ↗CrossEncoder: [query + 文档] → [深度交互] → 分数 0.924️⃣位置感知加权融合
这是什么意思?
根据文档在 RRF 榜单中的排名位置,动态调整"RRF 分数"和"重排分数"的权重比例。
🎯 **完整流程串联 **
场景:用户搜索"苹果手机多少钱"
Step 1: 双路并行检索
BM25 路径(关键词匹配): "苹果手机价格" → 第 1 名(包含完整关键词)"苹果报价" → 第 2 名(包含"苹果")"手机多少钱" → 第 3 名(包含"手机")向量路径(语义匹配):"iPhone 15 售价" → 第 1 名(语义等价)"苹果手机价格" → 第 2 名(语义 + 关键词)"华为手机报价" → 第 3 名(都是手机)两路同时跑,耗时从 100ms 降到 50ms!Step 2: RRF 融合
文档"苹果手机价格": BM25 排名第 1 → RRF 分数 = 1/(60+1+1) = 0.0161 向量排名第 2 → RRF 分数 = 1/(60+2+1) = 0.0159 合计 = 0.032,排总榜第 1 因为是冠军,额外奖励 +0.05 → 最终 0.082文档"iPhone 15 售价": BM25 没进前 10 → 0 分 向量排名第 1 → 1/(60+1+1) = 0.0161 合计 = 0.0161,排总榜第 2取 Top30 进入下一轮...Step 3: CrossEncoder 重排
对 Top30 逐个精细打分: "苹果手机价格" → 原始分 2.5 → sigmoid 后 0.92 → 高度相关 ⭐⭐⭐⭐⭐ "iPhone 15 售价" → 原始分 1.8 → sigmoid 后 0.86 → 高度相关 ⭐⭐⭐⭐⭐ "华为手机报价" → 原始分 0.3 → sigmoid 后 0.57 → 中等相关 ⭐⭐⭐Step 4: 位置感知加权
Top1 "苹果手机价格": RRF 分数=0.082,重排分=0.92 最终分 = 0.082×0.75 + 0.92×0.25 = 0.292Top2 "iPhone 15 售价": RRF 分数=0.016,重排分=0.86 最终分 = 0.016×0.75 + 0.86×0.25 = 0.227Top15 "苹果官方公告"(后排逆袭): RRF 分数=0.008,重排分=0.95 最终分 = 0.008×0.40 + 0.95×0.60 = 0.573 ← 直接冲进前 3!二、此次升级整体改动
2.1 新旧架构对比
【旧架构】单路向量检索 Query → Embedding → Milvus Search → TopK → Context【新架构】混合检索流水线 (8 Stages) Query ──┬→ Milvus 向量召回 ──┐ └→ BM25 关键词召回 ──┘→ 合并 → RRF融合 → 归一化 → 重排 → 自适应融合 → 截断 → 组装2.2 新增模块总览
src/main/java/com/isy/rag/bootstrap/rag/retrieval/kb/├── assemble/│ └── KbContextAssembler.java # Stage8: 上下文组装器├── blend/│ └── ConfidenceAdaptiveScoreBlender.java # Stage6: 自适应分数融合器├── config/│ ├── ElasticsearchConfig.java # ES 客户端配置│ └── KbRetrievalProperties.java # 混合检索配置项 (rag.kb.hybrid.*)├── cutoff/│ └── KbResultCutoff.java # Stage7: TopK + 阈值双重截断器├── es/│ └── KbEsIndexService.java # ES 索引服务 (BM25 + IK分词)├── fusion/│ ├── KbRrfFusion.java # Stage3: RRF 融合器│ └── ScoreNormalizer.java # Stage4: Min-Max 归一化器├── merge/│ └── KbCandidateMerger.java # Stage2: 同chunk候选合并器├── model/│ ├── KbCandidate.java # 候选统一模型 (6类分数)│ ├── KbCandidateSource.java # 命中来源枚举 (VECTOR/BM25/BOTH)│ ├── KbRetrievalRequest.java # 检索请求 (含effective默认值)│ ├── KbRetrievalResult.java # 检索结果 (含阶段耗时统计)│ └── RelevanceLevel.java # 相关性等级枚举├── pipeline/│ └── KbHybridRetrievalPipeline.java # 8阶段流水线编排器├── rerank/│ ├── KbReranker.java # 重排器接口│ ├── KbRerankerConfig.java # 重排器 Spring 配置 (条件注入)│ ├── NoopKbReranker.java # 空实现降级│ └── RemoteKbReranker.java # 远程 CrossEncoder 重排└── retriever/ ├── KbRetriever.java # 召回器接口 ├── KbBm25Retriever.java # BM25 召回 (ES/MySQL双后端) └── KbVectorRetriever.java # 向量召回 (Milvus)三、8 阶段流水线详细设计
Stage 1: 并行混合召回
设计思路:向量语义召回 + BM25 关键词召回并行执行,互不阻塞
实现要点:
- 使用
CompletableFuture+ 固定2线程池并行执行 - 异常隔离:单路失败不影响另一路,记录 warning 继续流程
- 向量召回:通过
EmbeddingModel生成查询向量 → Milvus ANN 检索 → 赋 vectorRank - BM25 召回:支持双后端(
elasticsearch/mysql),通过配置切换
- ES 后端:增量同步 chunk → ES BM25 + IK 分词,适合生产环境
- MySQL 后端:全量加载 chunk → 内存计算 BM25,适合开发/测试
关键代码:
KbVectorRetriever.retrieve()— 向量召回KbBm25Retriever.retrieve()— BM25 召回,ES 失败自动降级到 MySQL
Stage 2: 同 Chunk 候选合并
设计思路:同一 chunk 被两路同时召回时,保留双路所有分数信息
实现要点:
- 按
chunkId合并,KbCandidateMerger.mergeAll()处理多列表合并 - 合并时使用
coalesce策略:优先使用已有数据 - 合并后自动更新
matchedBy字段:VECTOR/BM25/BOTH - 双路命中的 chunk 在后续 RRF 融合中将获得更高分数
Stage 3: RRF 融合
设计思路:基于排名的融合 (Reciprocal Rank Fusion),公平合并不同检索器的结果
公式:
rrfScore(d) = Σ weight_i × (1 / (k + rank_i) + bonus(rank_i))创新点 — bonus 融入公式:
- 传统 RRF 只用
1/(k+rank),本方案将 rank1/rank2-3 奖励直接融入公式 - 避免了"先排序 → 加分 → 再排序"的二次排序问题
- 默认参数:
rrfK=60,vectorWeight=0.7,bm25Weight=0.3,rank1Bonus=0.05,rank2To3Bonus=0.02
为什么用 RRF 而非直接加权融合:
- 向量分数范围
[0, 1],BM25 分数范围[0, +∞),量纲完全不同 - RRF 基于排名计算,天然消除了分数量纲不一致的问题
Stage 4: RRF 分数归一化
设计思路:将 RRF 原始分数映射到[0, 1],为后续与 rerank 分数融合做准备
公式:
normalizedRrf = (rrfScore - minRrf) / (maxRrf - minRrf)边界处理:
- 所有分数相等时统一设为
1.0 rrfScore为 null 的候选跳过归一化
Stage 5: CrossEncoder 重排
设计思路:对候选进行 query-document 对的语义精排,弥补检索阶段"只看单路"的不足
实现要点:
- 远程重排:调用
bge-rerankerFastAPI 服务,Python 端已做 Sigmoid 归一化 - Spring 条件注入:配置了
reranker-url时创建RemoteKbReranker,否则NoopKbReranker - 降级策略:远程调用失败时自动降级,使用
normalizedRrfScore作为rerankScore - 重排后为每个候选设置
RelevanceLevel:高度相关(≥0.8) / 中等相关(≥0.5) / 一般相关(≥0.2) / 低相关(<0.2)
Stage 6: 自适应分数融合
设计思路:替代固定位置权重,根据 CrossEncoder 的置信度动态调整融合权重
核心洞察:
- Sigmoid 输出
0.5表示模型最不确定 - 离
0.5越远,模型越自信,应赋予更高权重
公式:
certainty = 2 × |rerankScore - 0.5|rerankerWeight = baseRerankerWeight + certainty × adaptiveFactorrerankerWeight = clamp(rerankerWeight, minRerankerWeight, maxRerankerWeight)finalScore = normalizedRrfScore × (1 - rerankerWeight) + rerankScore × rerankerWeight默认参数:baseRerankerWeight=0.35,adaptiveFactor=0.35,min=0.30,max=0.75
效果:
- 当重排模型高度自信时(score 接近 0 或 1),重排权重最高可达 0.75
- 当重排模型不确定时(score 接近 0.5),重排权重最低降到 0.30,更多依赖检索分数
Stage 7: TopK + 阈值双重截断
设计思路:双重防线过滤低质量分块,防止噪声进入 LLM 上下文
过滤条件(按顺序):
finalScore >= finalScoreThreshold(默认 0.45)rerankScore >= rerankThreshold(默认 0.55,无 rerankScore 时跳过)- 数量不超过
maxChunks
Stage 8: 上下文组装
设计思路:将最终候选组装为 LLM 可消费的上下文 + 前端可展示的引用信息
实现要点:
- chunkId 去重(防止重复)
- 按
finalScore降序排列 - 上下文格式:
【文档N:xxx.pdf | chunk=3 | score=0.82】\n内容\n - 最大字符数限制(默认 8000)
- 引用信息携带完整分数字段:
vector_score,bm25_score,rrf_score,normalized_rrf_score,rerank_score,final_score,relevance_level,matched_by
四、数据模型设计
4.1 KbCandidate — 候选统一模型
一个KbCandidate在流水线中流转,逐阶段积累分数:
Stage1: chunkId, docId, docName, content, vectorScore, vectorRank, bm25Score, bm25Rank, matchedByStage2: (合并后) matchedBy 更新为 BOTHStage3: rrfScoreStage4: normalizedRrfScoreStage5: rerankRawScore, rerankScore, relevanceLevelStage6: finalScore6 类分数的生命周期:
| 分数 | 产生阶段 | 范围 | 用途 |
|---|---|---|---|
vectorScore | Stage1 | [0, 1] | 向量相似度原始分 |
bm25Score | Stage1 | [0, +∞) | BM25 原始分 |
rrfScore | Stage3 | (0, 0.1] | RRF 融合原始分 |
normalizedRrfScore | Stage4 | [0, 1] | RRF 归一化分 |
rerankScore | Stage5 | [0, 1] | CrossEncoder 重排分 |
finalScore | Stage6 | [0, 1] | 最终融合分(排序/截断依据) |
4.2 KbCandidateSource — 命中来源
| 枚举值 | 含义 | 前端标签 | 样式 |
|---|---|---|---|
VECTOR | 仅向量命中 | 向量召回 | 蓝色 |
BM25 | 仅关键词命中 | 关键词召回 | 橙色 |
BOTH | 双路同时命中 | 双路命中 | 紫色 |
4.3 RelevanceLevel — 相关性等级
| 等级 | 分数范围 | 是否纳入 | 前端标签 |
|---|---|---|---|
HIGHLY_RELEVANT | ≥ 0.8 | 是 | 高度相关 (绿) |
MODERATELY_RELEVANT | ≥ 0.5 | 是 | 中等相关 (蓝) |
SOMEWHAT_RELEVANT | ≥ 0.2 | 否 | 一般相关 (黄) |
LOW_RELEVANCE | < 0.2 | 否 | 低相关 (红) |
五、配置体系设计
5.1 application.yml 配置
rag: kb: hybrid: enabled:true # 混合检索开关 vector-top-k:30 # 向量召回数 bm25-top-k:30 # BM25 召回数 bm25-backend:elasticsearch# BM25 后端 (elasticsearch / mysql) rrf-top-n:30 # RRF 融合保留数 rrf-k:60 # RRF 常数 k vector-weight:0.7 # 向量权重 bm25-weight:0.3 # BM25 权重 rank1-bonus:0.05 # Rank1 奖励 rank2-to3-bonus:0.02 # Rank2-3 奖励 rerank-enabled:true # 重排开关 reranker-url:http://localhost:8100/rerank# 重排服务地址 rerank-top-n:30 # 重排候选数 rerank-threshold:0.55 # 重排分数阈值 final-score-threshold:0.45# 最终分数阈值 base-reranker-weight:0.35# 基础重排权重 adaptive-factor:0.35 # 自适应因子 min-reranker-weight:0.30# 最小重排权重 max-reranker-weight:0.75# 最大重排权重5.2 降级策略
| 外部服务 | 配置调整 | 降级行为 |
|---|---|---|
| Milvus 不可用 | — | 向量召回返回空列表,BM25 单路工作 |
| ES 不可用 | bm25-backend: mysql | 自动降级到 MySQL 内存 BM25 |
| Reranker 不可用 | 注释reranker-url | NoopKbReranker生效,用归一化 RRF 分数代替 |
| 全部不可用 | hybrid.enabled: false | 回退到传统纯向量检索 |
六、前端展示增强
6.1 引用卡片增强
每个文档引用卡片新增:
- 命中来源标签:
向量召回/关键词召回/双路命中(彩色标签) - 相关性等级:
高度相关/中等相关/一般相关/低相关(分级标签) - 最终分数:显示
finalScore数值 - 分数详情展开:显示完整分数链
向量分 → BM25分 → RRF分 → 重排分 → 最终分
6.2 管理后台 Bot 配置增强
- Bot 配置页面新增混合检索配置项展示
- 追踪详情页新增
KB_RETRIEVE节点的检索模式标识
七、可观测性设计
7.1 统一日志标识
全链路使用[KB检索]前缀,支持grep "[KB检索]"一键过滤:
| 日志标识 | 打印内容 |
|---|---|
[KB检索][入口] | query, kbIds, maxChunks |
[KB检索][模式选择] | hybridEnabled, bm25Backend, 选择模式 |
[KB检索][Stage1-向量召回] | 召回数, topK, Top3 chunkId+vecScore+docName |
[KB检索][Stage1-BM25召回] | 召回数, topK, backend, Top3 chunkId+bm25Score+docName |
[KB检索][Stage2-候选合并] | 合并后数, 双路/仅向量/仅BM25 统计 |
[KB检索][Stage3-RRF融合] | 输入/输出数, rrfK, weights, Top3 rrfScore+matchedBy |
[KB检索][Stage4-分数归一化] | 候选数, 归一化分数范围 [min, max] |
[KB检索][Stage5-重排] | reranker名称, 候选数, Top3 rerankScore+level |
[KB检索][Stage6-自适应融合] | 候选数, weights, Top3 normRrf+rerank+final |
[KB检索][Stage7-截断] | 输入/输出/截断数, 阈值, 逐条最终候选摘要 |
[KB检索][Stage8-上下文组装] | 候选数, 引用数, 字符数 |
[KB检索][总出口] | mode, confidence, chunkCount, duration |
[KB检索][出口] | confidence, refCount, contextLen, duration |
7.2 链路追踪
每个 Stage 的耗时通过KbRetrievalResult.recordStageDuration()记录,通过TraceEventPublisher发布到追踪系统,在管理后台的追踪详情页可查看。
八、新增依赖
<!-- Elasticsearch Java Client --><dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.15.0</version></dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId></dependency><dependency> <groupId>jakarta.json</groupId> <artifactId>jakarta.json-api</artifactId> <version>2.1.3</version></dependency>学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
