Commit c31b66b6cc675c9edb26ea03af0dbecb27edf792
1 parent
45a9dfc4
feat:【ai 大模型】RAG 增加 rerank 模型
Showing
3 changed files
with
95 additions
and
18 deletions
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
| @@ -18,6 +18,10 @@ import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; | @@ -18,6 +18,10 @@ import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; | ||
| 18 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; | 18 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| 19 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; | 19 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; |
| 20 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; | 20 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| 21 | +import com.alibaba.cloud.ai.dashscope.rerank.DashScopeRerankOptions; | ||
| 22 | +import com.alibaba.cloud.ai.model.RerankModel; | ||
| 23 | +import com.alibaba.cloud.ai.model.RerankRequest; | ||
| 24 | +import com.alibaba.cloud.ai.model.RerankResponse; | ||
| 21 | import jakarta.annotation.Resource; | 25 | import jakarta.annotation.Resource; |
| 22 | import lombok.extern.slf4j.Slf4j; | 26 | import lombok.extern.slf4j.Slf4j; |
| 23 | import org.springframework.ai.document.Document; | 27 | import org.springframework.ai.document.Document; |
| @@ -27,6 +31,7 @@ import org.springframework.ai.transformer.splitter.TokenTextSplitter; | @@ -27,6 +31,7 @@ import org.springframework.ai.transformer.splitter.TokenTextSplitter; | ||
| 27 | import org.springframework.ai.vectorstore.SearchRequest; | 31 | import org.springframework.ai.vectorstore.SearchRequest; |
| 28 | import org.springframework.ai.vectorstore.VectorStore; | 32 | import org.springframework.ai.vectorstore.VectorStore; |
| 29 | import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; | 33 | import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; |
| 34 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 30 | import org.springframework.context.annotation.Lazy; | 35 | import org.springframework.context.annotation.Lazy; |
| 31 | import org.springframework.stereotype.Service; | 36 | import org.springframework.stereotype.Service; |
| 32 | 37 | ||
| @@ -36,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | @@ -36,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | ||
| 36 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 41 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 37 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG; | 42 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG; |
| 38 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS; | 43 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS; |
| 44 | +import static org.springframework.ai.vectorstore.SearchRequest.SIMILARITY_THRESHOLD_ACCEPT_ALL; | ||
| 39 | 45 | ||
| 40 | /** | 46 | /** |
| 41 | * AI 知识库分片 Service 实现类 | 47 | * AI 知识库分片 Service 实现类 |
| @@ -55,6 +61,11 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -55,6 +61,11 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 55 | VECTOR_STORE_METADATA_DOCUMENT_ID, String.class, | 61 | VECTOR_STORE_METADATA_DOCUMENT_ID, String.class, |
| 56 | VECTOR_STORE_METADATA_SEGMENT_ID, String.class); | 62 | VECTOR_STORE_METADATA_SEGMENT_ID, String.class); |
| 57 | 63 | ||
| 64 | + /** | ||
| 65 | + * Rerank 在向量检索时,检索数量 * 该系数,目的是为了提升 Rerank 的效果 | ||
| 66 | + */ | ||
| 67 | + private static final Integer RERANK_RETRIEVAL_FACTOR = 4; | ||
| 68 | + | ||
| 58 | @Resource | 69 | @Resource |
| 59 | private AiKnowledgeSegmentMapper segmentMapper; | 70 | private AiKnowledgeSegmentMapper segmentMapper; |
| 60 | 71 | ||
| @@ -69,6 +80,9 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -69,6 +80,9 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 69 | @Resource | 80 | @Resource |
| 70 | private TokenCountEstimator tokenCountEstimator; | 81 | private TokenCountEstimator tokenCountEstimator; |
| 71 | 82 | ||
| 83 | + @Autowired(required = false) // 由于 spring.ai.model.rerank 配置项,可以关闭 RerankModel 的功能,所以这里只能不强制注入 | ||
| 84 | + private RerankModel rerankModel; | ||
| 85 | + | ||
| 72 | @Override | 86 | @Override |
| 73 | public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { | 87 | public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { |
| 74 | return segmentMapper.selectPage(pageReqVO); | 88 | return segmentMapper.selectPage(pageReqVO); |
| @@ -211,28 +225,16 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -211,28 +225,16 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 211 | // 1. 校验 | 225 | // 1. 校验 |
| 212 | AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId()); | 226 | AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId()); |
| 213 | 227 | ||
| 214 | - // 2.1 向量检索 | ||
| 215 | - VectorStore vectorStore = getVectorStoreById(knowledge); | ||
| 216 | - List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder() | ||
| 217 | - .query(reqBO.getContent()) | ||
| 218 | - .topK(ObjUtil.defaultIfNull(reqBO.getTopK(), knowledge.getTopK())) | ||
| 219 | - .similarityThreshold( | ||
| 220 | - ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold())) | ||
| 221 | - .filterExpression(new FilterExpressionBuilder() | ||
| 222 | - .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId().toString()) | ||
| 223 | - .build()) | ||
| 224 | - .build()); | ||
| 225 | - if (CollUtil.isEmpty(documents)) { | ||
| 226 | - return ListUtil.empty(); | ||
| 227 | - } | ||
| 228 | - // 2.2 段落召回 | 228 | + // 2. 检索 |
| 229 | + List<Document> documents = searchDocument(knowledge, reqBO); | ||
| 230 | + | ||
| 231 | + // 3.1 段落召回 | ||
| 229 | List<AiKnowledgeSegmentDO> segments = segmentMapper | 232 | List<AiKnowledgeSegmentDO> segments = segmentMapper |
| 230 | .selectListByVectorIds(convertList(documents, Document::getId)); | 233 | .selectListByVectorIds(convertList(documents, Document::getId)); |
| 231 | if (CollUtil.isEmpty(segments)) { | 234 | if (CollUtil.isEmpty(segments)) { |
| 232 | return ListUtil.empty(); | 235 | return ListUtil.empty(); |
| 233 | } | 236 | } |
| 234 | - | ||
| 235 | - // 3. 增加召回次数 | 237 | + // 3.2 增加召回次数 |
| 236 | segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId)); | 238 | segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId)); |
| 237 | 239 | ||
| 238 | // 4. 构建结果 | 240 | // 4. 构建结果 |
| @@ -249,6 +251,42 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -249,6 +251,42 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 249 | return result; | 251 | return result; |
| 250 | } | 252 | } |
| 251 | 253 | ||
| 254 | + /** | ||
| 255 | + * 基于 Embedding + Rerank Model,检索知识库中的文档 | ||
| 256 | + * | ||
| 257 | + * @param knowledge 知识库 | ||
| 258 | + * @param reqBO 检索请求 | ||
| 259 | + * @return 文档列表 | ||
| 260 | + */ | ||
| 261 | + private List<Document> searchDocument(AiKnowledgeDO knowledge, AiKnowledgeSegmentSearchReqBO reqBO) { | ||
| 262 | + VectorStore vectorStore = getVectorStoreById(knowledge); | ||
| 263 | + Integer topK = ObjUtil.defaultIfNull(reqBO.getTopK(), knowledge.getTopK()); | ||
| 264 | + Double similarityThreshold = ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold()); | ||
| 265 | + | ||
| 266 | + // 1. 向量检索 | ||
| 267 | + int searchTopK = rerankModel != null ? topK * RERANK_RETRIEVAL_FACTOR : topK; | ||
| 268 | + double searchSimilarityThreshold = rerankModel != null ? SIMILARITY_THRESHOLD_ACCEPT_ALL : similarityThreshold; | ||
| 269 | + SearchRequest.Builder searchRequestBuilder = SearchRequest.builder() | ||
| 270 | + .query(reqBO.getContent()) | ||
| 271 | + .topK(searchTopK).similarityThreshold(searchSimilarityThreshold) | ||
| 272 | + .filterExpression(new FilterExpressionBuilder() | ||
| 273 | + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId().toString()).build()); | ||
| 274 | + List<Document> documents = vectorStore.similaritySearch(searchRequestBuilder.build()); | ||
| 275 | + if (CollUtil.isEmpty(documents)) { | ||
| 276 | + return documents; | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + // 2. Rerank 重排序 | ||
| 280 | + if (rerankModel != null) { | ||
| 281 | + RerankResponse rerankResponse = rerankModel.call(new RerankRequest(reqBO.getContent(), documents, | ||
| 282 | + DashScopeRerankOptions.builder().withTopN(topK).build())); | ||
| 283 | + documents = convertList(rerankResponse.getResults(), | ||
| 284 | + documentWithScore -> documentWithScore.getScore() >= similarityThreshold | ||
| 285 | + ? documentWithScore.getOutput() : null); | ||
| 286 | + } | ||
| 287 | + return documents; | ||
| 288 | + } | ||
| 289 | + | ||
| 252 | @Override | 290 | @Override |
| 253 | public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) { | 291 | public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) { |
| 254 | // 1. 读取 URL 内容 | 292 | // 1. 读取 URL 内容 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 3 | import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; | 4 | import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; |
| 4 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; | 5 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; |
| 5 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; | 6 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; |
| 7 | +import com.alibaba.cloud.ai.dashscope.rerank.DashScopeRerankModel; | ||
| 8 | +import com.alibaba.cloud.ai.dashscope.rerank.DashScopeRerankOptions; | ||
| 9 | +import com.alibaba.cloud.ai.model.RerankModel; | ||
| 10 | +import com.alibaba.cloud.ai.model.RerankOptions; | ||
| 11 | +import com.alibaba.cloud.ai.model.RerankRequest; | ||
| 12 | +import com.alibaba.cloud.ai.model.RerankResponse; | ||
| 6 | import org.junit.jupiter.api.Disabled; | 13 | import org.junit.jupiter.api.Disabled; |
| 7 | import org.junit.jupiter.api.Test; | 14 | import org.junit.jupiter.api.Test; |
| 8 | import org.springframework.ai.chat.messages.Message; | 15 | import org.springframework.ai.chat.messages.Message; |
| @@ -10,11 +17,14 @@ import org.springframework.ai.chat.messages.SystemMessage; | @@ -10,11 +17,14 @@ import org.springframework.ai.chat.messages.SystemMessage; | ||
| 10 | import org.springframework.ai.chat.messages.UserMessage; | 17 | import org.springframework.ai.chat.messages.UserMessage; |
| 11 | import org.springframework.ai.chat.model.ChatResponse; | 18 | import org.springframework.ai.chat.model.ChatResponse; |
| 12 | import org.springframework.ai.chat.prompt.Prompt; | 19 | import org.springframework.ai.chat.prompt.Prompt; |
| 20 | +import org.springframework.ai.document.Document; | ||
| 13 | import reactor.core.publisher.Flux; | 21 | import reactor.core.publisher.Flux; |
| 14 | 22 | ||
| 15 | import java.util.ArrayList; | 23 | import java.util.ArrayList; |
| 16 | import java.util.List; | 24 | import java.util.List; |
| 17 | 25 | ||
| 26 | +import static java.util.Arrays.asList; | ||
| 27 | + | ||
| 18 | /** | 28 | /** |
| 19 | * {@link DashScopeChatModel} 集成测试类 | 29 | * {@link DashScopeChatModel} 集成测试类 |
| 20 | * | 30 | * |
| @@ -89,4 +99,31 @@ public class TongYiChatModelTests { | @@ -89,4 +99,31 @@ public class TongYiChatModelTests { | ||
| 89 | }).then().block(); | 99 | }).then().block(); |
| 90 | } | 100 | } |
| 91 | 101 | ||
| 102 | + @Test | ||
| 103 | + @Disabled | ||
| 104 | + public void testRerank() { | ||
| 105 | + // 准备环境 | ||
| 106 | + RerankModel rerankModel = new DashScopeRerankModel( | ||
| 107 | + DashScopeApi.builder() | ||
| 108 | + .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0") | ||
| 109 | + .build()); | ||
| 110 | + // 准备参数 | ||
| 111 | + String query = "spring"; | ||
| 112 | + Document document01 = new Document("abc"); | ||
| 113 | + Document document02 = new Document("sapring"); | ||
| 114 | + RerankOptions options = DashScopeRerankOptions.builder() | ||
| 115 | + .withTopN(1) | ||
| 116 | + .withModel("gte-rerank-v2") | ||
| 117 | + .build(); | ||
| 118 | + RerankRequest rerankRequest = new RerankRequest( | ||
| 119 | + query, | ||
| 120 | + asList(document01, document02), | ||
| 121 | + options); | ||
| 122 | + | ||
| 123 | + // 调用 | ||
| 124 | + RerankResponse call = rerankModel.call(rerankRequest); | ||
| 125 | + // 打印结果 | ||
| 126 | + System.out.println(JsonUtils.toJsonPrettyString(call)); | ||
| 127 | + } | ||
| 128 | + | ||
| 92 | } | 129 | } |
yudao-server/src/main/resources/application.yaml
| @@ -184,7 +184,7 @@ spring: | @@ -184,7 +184,7 @@ spring: | ||
| 184 | stabilityai: | 184 | stabilityai: |
| 185 | api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx | 185 | api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx |
| 186 | dashscope: # 通义千问 | 186 | dashscope: # 通义千问 |
| 187 | - api-key: sk-71800982914041848008480000000000 | 187 | + api-key: sk-47aa124781be4bfb95244cc62f6xxxx |
| 188 | minimax: # Minimax:https://www.minimaxi.com/ | 188 | minimax: # Minimax:https://www.minimaxi.com/ |
| 189 | api-key: xxxx | 189 | api-key: xxxx |
| 190 | moonshot: # 月之暗灭(KIMI) | 190 | moonshot: # 月之暗灭(KIMI) |
| @@ -194,6 +194,8 @@ spring: | @@ -194,6 +194,8 @@ spring: | ||
| 194 | chat: | 194 | chat: |
| 195 | options: | 195 | options: |
| 196 | model: deepseek-chat | 196 | model: deepseek-chat |
| 197 | + model: | ||
| 198 | + rerank: dashscope # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启 | ||
| 197 | 199 | ||
| 198 | yudao: | 200 | yudao: |
| 199 | ai: | 201 | ai: |