Commit c31b66b6cc675c9edb26ea03af0dbecb27edf792

Authored by YunaiV
1 parent 45a9dfc4

feat:【ai 大模型】RAG 增加 rerank 模型

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 18 import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
19 19 import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
20 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 25 import jakarta.annotation.Resource;
22 26 import lombok.extern.slf4j.Slf4j;
23 27 import org.springframework.ai.document.Document;
... ... @@ -27,6 +31,7 @@ import org.springframework.ai.transformer.splitter.TokenTextSplitter;
27 31 import org.springframework.ai.vectorstore.SearchRequest;
28 32 import org.springframework.ai.vectorstore.VectorStore;
29 33 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
  34 +import org.springframework.beans.factory.annotation.Autowired;
30 35 import org.springframework.context.annotation.Lazy;
31 36 import org.springframework.stereotype.Service;
32 37  
... ... @@ -36,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
36 41 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
37 42 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG;
38 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 47 * AI 知识库分片 Service 实现类
... ... @@ -55,6 +61,11 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
55 61 VECTOR_STORE_METADATA_DOCUMENT_ID, String.class,
56 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 69 @Resource
59 70 private AiKnowledgeSegmentMapper segmentMapper;
60 71  
... ... @@ -69,6 +80,9 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
69 80 @Resource
70 81 private TokenCountEstimator tokenCountEstimator;
71 82  
  83 + @Autowired(required = false) // 由于 spring.ai.model.rerank 配置项,可以关闭 RerankModel 的功能,所以这里只能不强制注入
  84 + private RerankModel rerankModel;
  85 +
72 86 @Override
73 87 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
74 88 return segmentMapper.selectPage(pageReqVO);
... ... @@ -211,28 +225,16 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
211 225 // 1. 校验
212 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 232 List<AiKnowledgeSegmentDO> segments = segmentMapper
230 233 .selectListByVectorIds(convertList(documents, Document::getId));
231 234 if (CollUtil.isEmpty(segments)) {
232 235 return ListUtil.empty();
233 236 }
234   -
235   - // 3. 增加召回次数
  237 + // 3.2 增加召回次数
236 238 segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
237 239  
238 240 // 4. 构建结果
... ... @@ -249,6 +251,42 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
249 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 290 @Override
253 291 public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) {
254 292 // 1. 读取 URL 内容
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
1 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 4 import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
4 5 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
5 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 13 import org.junit.jupiter.api.Disabled;
7 14 import org.junit.jupiter.api.Test;
8 15 import org.springframework.ai.chat.messages.Message;
... ... @@ -10,11 +17,14 @@ import org.springframework.ai.chat.messages.SystemMessage;
10 17 import org.springframework.ai.chat.messages.UserMessage;
11 18 import org.springframework.ai.chat.model.ChatResponse;
12 19 import org.springframework.ai.chat.prompt.Prompt;
  20 +import org.springframework.ai.document.Document;
13 21 import reactor.core.publisher.Flux;
14 22  
15 23 import java.util.ArrayList;
16 24 import java.util.List;
17 25  
  26 +import static java.util.Arrays.asList;
  27 +
18 28 /**
19 29 * {@link DashScopeChatModel} 集成测试类
20 30 *
... ... @@ -89,4 +99,31 @@ public class TongYiChatModelTests {
89 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 184 stabilityai:
185 185 api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx
186 186 dashscope: # 通义千问
187   - api-key: sk-71800982914041848008480000000000
  187 + api-key: sk-47aa124781be4bfb95244cc62f6xxxx
188 188 minimax: # Minimax:https://www.minimaxi.com/
189 189 api-key: xxxx
190 190 moonshot: # 月之暗灭(KIMI)
... ... @@ -194,6 +194,8 @@ spring:
194 194 chat:
195 195 options:
196 196 model: deepseek-chat
  197 + model:
  198 + rerank: dashscope # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启
197 199  
198 200 yudao:
199 201 ai:
... ...