Commit d7e801c438d3e4c2e7f4e34cec224dd23b4a3efc

Authored by YunaiV
1 parent 32e1ef4d

【功能新增】AI:知识库文档的模型变化时,触发重索引(异步)

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
... ... @@ -42,6 +42,11 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment
42 42 .orderByDesc(AiKnowledgeSegmentDO::getId));
43 43 }
44 44  
  45 + default List<AiKnowledgeSegmentDO> selectListByKnowledgeIdAndStatus(Long knowledgeId, Integer status) {
  46 + return selectList(AiKnowledgeSegmentDO::getKnowledgeId, knowledgeId,
  47 + AiKnowledgeSegmentDO::getStatus, status);
  48 + }
  49 +
45 50 default List<AiKnowledgeSegmentProcessRespVO> selectProcessList(Collection<Long> documentIds) {
46 51 MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>()
47 52 .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId)
... ... @@ -54,7 +59,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
54 59 }
55 60  
56 61 default void updateRetrievalCountIncrByIds(List<Long> ids) {
57   - update( new LambdaUpdateWrapper<AiKnowledgeSegmentDO>()
  62 + update(new LambdaUpdateWrapper<AiKnowledgeSegmentDO>()
58 63 .setSql(" retrieval_count = retrieval_count + 1")
59 64 .in(AiKnowledgeSegmentDO::getId, ids));
60 65 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
... ... @@ -99,6 +99,23 @@ public interface AiKnowledgeSegmentService {
99 99 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
100 100  
101 101 /**
  102 + * 重新索引知识库下的所有文档段落
  103 + *
  104 + * @param knowledgeId 知识库编号
  105 + */
  106 + void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId);
  107 +
  108 + /**
  109 + * 【异步】重新索引知识库下的所有文档段落
  110 + *
  111 + * @param knowledgeId 知识库编号
  112 + */
  113 + @Async
  114 + default void reindexByKnowledgeIdAsync(Long knowledgeId) {
  115 + reindexKnowledgeSegmentByKnowledgeId(knowledgeId);
  116 + }
  117 +
  118 + /**
102 119 * 根据文档编号删除段落
103 120 *
104 121 * @param documentId 文档编号
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
... ... @@ -158,6 +158,30 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
158 158 }
159 159 }
160 160  
  161 + @Override
  162 + public void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId) {
  163 + // 1.1 校验知识库存在
  164 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
  165 + // 1.2 获取知识库向量实例
  166 + VectorStore vectorStore = getVectorStoreById(knowledge);
  167 +
  168 + // 2.1 查询知识库下的所有启用状态的段落
  169 + List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByKnowledgeIdAndStatus(
  170 + knowledgeId, CommonStatusEnum.ENABLE.getStatus());
  171 + if (CollUtil.isEmpty(segments)) {
  172 + return;
  173 + }
  174 + // 2.2 遍历所有段落,重新索引
  175 + for (AiKnowledgeSegmentDO segment : segments) {
  176 + // 删除旧的向量
  177 + deleteVectorStore(vectorStore, segment);
  178 + // 重新创建向量
  179 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
  180 + }
  181 + log.info("[reindexKnowledgeSegmentByKnowledgeId][知识库({}) 重新索引完成,共处理 {} 个段落]",
  182 + knowledgeId, segments.size());
  183 + }
  184 +
161 185 private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
162 186 // 1. 向量存储
163 187 // 为什么要 toString 呢?因为部分 VectorStore 实现,不支持 Long 类型,例如说 QdrantVectorStore
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
6 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
7 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
8 11 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
9 12 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
10 13 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
... ... @@ -31,6 +34,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
31 34  
32 35 @Resource
33 36 private AiModelService modelService;
  37 + @Resource
  38 + private AiKnowledgeSegmentService knowledgeSegmentService;
34 39  
35 40 @Override
36 41 public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) {
... ... @@ -47,7 +52,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
47 52 @Override
48 53 public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) {
49 54 // 1.1 校验知识库存在
50   - validateKnowledgeExists(updateReqVO.getId());
  55 + AiKnowledgeDO oldKnowledge = validateKnowledgeExists(updateReqVO.getId());
51 56 // 1.2 校验模型配置
52 57 AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId());
53 58  
... ... @@ -56,7 +61,10 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
56 61 .setEmbeddingModel(model.getModel());
57 62 knowledgeMapper.updateById(updateObj);
58 63  
59   - // TODO @芋艿:如果模型变化,需要 reindex 所有的文档
  64 + // 3. 如果模型变化,需要 reindex 所有的文档
  65 + if (ObjUtil.notEqual(oldKnowledge.getEmbeddingModelId(), updateReqVO.getEmbeddingModelId())) {
  66 + knowledgeSegmentService.reindexByKnowledgeIdAsync(updateReqVO.getId());
  67 + }
60 68 }
61 69  
62 70 @Override
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
... ... @@ -16,8 +16,8 @@ import jakarta.annotation.Resource;
16 16 import org.springframework.ai.chat.model.ChatModel;
17 17 import org.springframework.ai.embedding.EmbeddingModel;
18 18 import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.SimpleVectorStore;
19 20 import org.springframework.ai.vectorstore.VectorStore;
20   -import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
21 21 import org.springframework.stereotype.Service;
22 22 import org.springframework.validation.annotation.Validated;
23 23  
... ... @@ -162,10 +162,10 @@ public class AiModelServiceImpl implements AiModelService {
162 162 platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel());
163 163  
164 164 // 创建或获取 VectorStore 对象
165   - // return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields);
  165 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields);
166 166 // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields);
167 167 // return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields);
168   - return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
  168 +// return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
169 169 }
170 170  
171 171 }
172 172 \ No newline at end of file
... ...