Commit 92d32b652e05639b6b9407930e9218b75cdbb8f7

Authored by xiaoxin
1 parent c26cecae

【新增】AI 知识库: 段落召回

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +
  7 +@Schema(description = "管理后台 - AI 知识库段落召回 Request VO")
  8 +@Data
  9 +public class AiKnowledgeSegmentSearchReqVO {
  10 +
  11 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  12 + private Long knowledgeId;
  13 +
  14 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
  15 + private String content;
  16 +
  17 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
  10 +import java.util.List;
  11 +
10 /** 12 /**
11 * AI 知识库-分片 Mapper 13 * AI 知识库-分片 Mapper
12 * 14 *
@@ -22,4 +24,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment @@ -22,4 +24,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment
22 .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword()) 24 .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
23 .orderByDesc(AiKnowledgeSegmentDO::getId)); 25 .orderByDesc(AiKnowledgeSegmentDO::getId));
24 } 26 }
  27 +
  28 + default List<AiKnowledgeSegmentDO> selectList(List<String> vectorIdList) {
  29 + return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
  30 + .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList)
  31 + .orderByDesc(AiKnowledgeSegmentDO::getId));
  32 + }
25 } 33 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
@@ -2,10 +2,13 @@ package cn.iocoder.yudao.module.ai.service.knowledge; @@ -2,10 +2,13 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
8 9
  10 +import java.util.List;
  11 +
9 /** 12 /**
10 * AI 知识库段落 Service 接口 13 * AI 知识库段落 Service 接口
11 * 14 *
@@ -35,4 +38,13 @@ public interface AiKnowledgeSegmentService { @@ -35,4 +38,13 @@ public interface AiKnowledgeSegmentService {
35 */ 38 */
36 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); 39 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
37 40
  41 +
  42 + /**
  43 + * 段落召回
  44 + *
  45 + * @param reqVO 召回请求信息
  46 + * @return 召回的段落
  47 + */
  48 + List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO);
  49 +
38 } 50 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.collection.ListUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; 9 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 12 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
9 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; 14 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
  15 +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  16 +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
10 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
11 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.ai.document.Document;
  20 +import org.springframework.ai.vectorstore.SearchRequest;
  21 +import org.springframework.ai.vectorstore.VectorStore;
  22 +import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
12 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
13 24
  25 +import java.util.List;
  26 +
14 /** 27 /**
15 * AI 知识库分片 Service 实现类 28 * AI 知识库分片 Service 实现类
16 * 29 *
@@ -23,6 +36,13 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -23,6 +36,13 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
23 @Resource 36 @Resource
24 private AiKnowledgeSegmentMapper segmentMapper; 37 private AiKnowledgeSegmentMapper segmentMapper;
25 38
  39 + @Resource
  40 + private AiKnowledgeService knowledgeService;
  41 + @Resource
  42 + private AiChatModelService chatModelService;
  43 + @Resource
  44 + private AiApiKeyService apiKeyService;
  45 +
26 @Override 46 @Override
27 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { 47 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
28 return segmentMapper.selectPage(pageReqVO); 48 return segmentMapper.selectPage(pageReqVO);
@@ -39,4 +59,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -39,4 +59,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
39 segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class)); 59 segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
40 // TODO @xin 1.禁用删除向量 2.启用重新向量化 60 // TODO @xin 1.禁用删除向量 2.启用重新向量化
41 } 61 }
  62 +
  63 + @Override
  64 + public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) {
  65 + // 0. 校验
  66 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId());
  67 + AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
  68 +
  69 + // 1.1 获取向量存储实例
  70 + VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
  71 +
  72 + // 1.2 向量检索
  73 + List<Document> documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent())
  74 + //TODO @xin 配置提取
  75 + .withTopK(5)
  76 + .withSimilarityThreshold(0.5d)
  77 + .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build()));
  78 + if (CollUtil.isEmpty(documentList)) {
  79 + return ListUtil.empty();
  80 + }
  81 + // 2.1 段落召回
  82 + return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class));
  83 + }
42 } 84 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
@@ -82,7 +82,7 @@ public class YudaoAiAutoConfiguration { @@ -82,7 +82,7 @@ public class YudaoAiAutoConfiguration {
82 // TODO @xin 免费版本 82 // TODO @xin 免费版本
83 // @Bean 83 // @Bean
84 // @Lazy // TODO 芋艿:临时注释,避免无法启动」 84 // @Lazy // TODO 芋艿:临时注释,避免无法启动」
85 -// public EmbeddingModel transformersEmbeddingClient() { 85 +// public TransformersEmbeddingModel transformersEmbeddingClient() {
86 // return new TransformersEmbeddingModel(MetadataMode.EMBED); 86 // return new TransformersEmbeddingModel(MetadataMode.EMBED);
87 // } 87 // }
88 88
@@ -91,23 +91,24 @@ public class YudaoAiAutoConfiguration { @@ -91,23 +91,24 @@ public class YudaoAiAutoConfiguration {
91 */ 91 */
92 // @Bean 92 // @Bean
93 // @Lazy // TODO 芋艿:临时注释,避免无法启动 93 // @Lazy // TODO 芋艿:临时注释,避免无法启动
94 -// public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties, 94 +// public RedisVectorStore vectorStore(TransformersEmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
95 // RedisProperties redisProperties) { 95 // RedisProperties redisProperties) {
96 // var config = RedisVectorStore.RedisVectorStoreConfig.builder() 96 // var config = RedisVectorStore.RedisVectorStoreConfig.builder()
97 // .withIndexName(properties.getIndex()) 97 // .withIndexName(properties.getIndex())
98 // .withPrefix(properties.getPrefix()) 98 // .withPrefix(properties.getPrefix())
  99 +// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
99 // .build(); 100 // .build();
100 // 101 //
101 -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel, 102 +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
102 // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), 103 // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
103 // properties.isInitializeSchema()); 104 // properties.isInitializeSchema());
104 // redisVectorStore.afterPropertiesSet(); 105 // redisVectorStore.afterPropertiesSet();
105 // return redisVectorStore; 106 // return redisVectorStore;
106 // } 107 // }
107 -  
108 @Bean 108 @Bean
109 @Lazy // TODO 芋艿:临时注释,避免无法启动 109 @Lazy // TODO 芋艿:临时注释,避免无法启动
110 public TokenTextSplitter tokenTextSplitter() { 110 public TokenTextSplitter tokenTextSplitter() {
  111 + //TODO @xin 配置提取
111 return new TokenTextSplitter(500, 100, 5, 10000, true); 112 return new TokenTextSplitter(500, 100, 5, 10000, true);
112 } 113 }
113 114
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
@@ -66,6 +66,7 @@ import org.springframework.retry.support.RetryTemplate; @@ -66,6 +66,7 @@ import org.springframework.retry.support.RetryTemplate;
66 import org.springframework.web.client.ResponseErrorHandler; 66 import org.springframework.web.client.ResponseErrorHandler;
67 import org.springframework.web.client.RestClient; 67 import org.springframework.web.client.RestClient;
68 import redis.clients.jedis.JedisPooled; 68 import redis.clients.jedis.JedisPooled;
  69 +import redis.clients.jedis.search.Schema;
69 70
70 import java.util.List; 71 import java.util.List;
71 72
@@ -200,14 +201,11 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -200,14 +201,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
200 public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { 201 public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) {
201 String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); 202 String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
202 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> { 203 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
203 - // TODO 芋艿 @xin 这两个配置取哪好呢  
204 - // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上  
205 - // TODO 回复:好的哈  
206 - String index = "default-index";  
207 - String prefix = "default:"; 204 + String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
208 var config = RedisVectorStore.RedisVectorStoreConfig.builder() 205 var config = RedisVectorStore.RedisVectorStoreConfig.builder()
209 - .withIndexName(index) 206 + .withIndexName(cacheKey)
210 .withPrefix(prefix) 207 .withPrefix(prefix)
  208 + .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
211 .build(); 209 .build();
212 RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); 210 RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
213 RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, 211 RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,