Commit d7e801c438d3e4c2e7f4e34cec224dd23b4a3efc
1 parent
32e1ef4d
【功能新增】AI:知识库文档的模型变化时,触发重索引(异步)
Showing
5 changed files
with
60 additions
and
6 deletions
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,6 +42,11 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment | ||
| 42 | .orderByDesc(AiKnowledgeSegmentDO::getId)); | 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 | default List<AiKnowledgeSegmentProcessRespVO> selectProcessList(Collection<Long> documentIds) { | 50 | default List<AiKnowledgeSegmentProcessRespVO> selectProcessList(Collection<Long> documentIds) { |
| 46 | MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>() | 51 | MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>() |
| 47 | .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId) | 52 | .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId) |
| @@ -54,7 +59,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment | @@ -54,7 +59,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment | ||
| 54 | } | 59 | } |
| 55 | 60 | ||
| 56 | default void updateRetrievalCountIncrByIds(List<Long> ids) { | 61 | default void updateRetrievalCountIncrByIds(List<Long> ids) { |
| 57 | - update( new LambdaUpdateWrapper<AiKnowledgeSegmentDO>() | 62 | + update(new LambdaUpdateWrapper<AiKnowledgeSegmentDO>() |
| 58 | .setSql(" retrieval_count = retrieval_count + 1") | 63 | .setSql(" retrieval_count = retrieval_count + 1") |
| 59 | .in(AiKnowledgeSegmentDO::getId, ids)); | 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,6 +99,23 @@ public interface AiKnowledgeSegmentService { | ||
| 99 | void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); | 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 | * @param documentId 文档编号 | 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,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 | private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) { | 185 | private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) { |
| 162 | // 1. 向量存储 | 186 | // 1. 向量存储 |
| 163 | // 为什么要 toString 呢?因为部分 VectorStore 实现,不支持 Long 类型,例如说 QdrantVectorStore | 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 | package cn.iocoder.yudao.module.ai.service.knowledge; | 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 | 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.knowledge.AiKnowledgePageReqVO; | 7 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; |
| 6 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO; | 8 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO; |
| 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | 9 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; |
| 10 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; | ||
| 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| 9 | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; | 12 | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; |
| 10 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; | 13 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| @@ -31,6 +34,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | @@ -31,6 +34,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | ||
| 31 | 34 | ||
| 32 | @Resource | 35 | @Resource |
| 33 | private AiModelService modelService; | 36 | private AiModelService modelService; |
| 37 | + @Resource | ||
| 38 | + private AiKnowledgeSegmentService knowledgeSegmentService; | ||
| 34 | 39 | ||
| 35 | @Override | 40 | @Override |
| 36 | public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) { | 41 | public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) { |
| @@ -47,7 +52,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | @@ -47,7 +52,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | ||
| 47 | @Override | 52 | @Override |
| 48 | public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) { | 53 | public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) { |
| 49 | // 1.1 校验知识库存在 | 54 | // 1.1 校验知识库存在 |
| 50 | - validateKnowledgeExists(updateReqVO.getId()); | 55 | + AiKnowledgeDO oldKnowledge = validateKnowledgeExists(updateReqVO.getId()); |
| 51 | // 1.2 校验模型配置 | 56 | // 1.2 校验模型配置 |
| 52 | AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId()); | 57 | AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId()); |
| 53 | 58 | ||
| @@ -56,7 +61,10 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | @@ -56,7 +61,10 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | ||
| 56 | .setEmbeddingModel(model.getModel()); | 61 | .setEmbeddingModel(model.getModel()); |
| 57 | knowledgeMapper.updateById(updateObj); | 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 | @Override | 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,8 +16,8 @@ import jakarta.annotation.Resource; | ||
| 16 | import org.springframework.ai.chat.model.ChatModel; | 16 | import org.springframework.ai.chat.model.ChatModel; |
| 17 | import org.springframework.ai.embedding.EmbeddingModel; | 17 | import org.springframework.ai.embedding.EmbeddingModel; |
| 18 | import org.springframework.ai.image.ImageModel; | 18 | import org.springframework.ai.image.ImageModel; |
| 19 | +import org.springframework.ai.vectorstore.SimpleVectorStore; | ||
| 19 | import org.springframework.ai.vectorstore.VectorStore; | 20 | import org.springframework.ai.vectorstore.VectorStore; |
| 20 | -import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; | ||
| 21 | import org.springframework.stereotype.Service; | 21 | import org.springframework.stereotype.Service; |
| 22 | import org.springframework.validation.annotation.Validated; | 22 | import org.springframework.validation.annotation.Validated; |
| 23 | 23 | ||
| @@ -162,10 +162,10 @@ public class AiModelServiceImpl implements AiModelService { | @@ -162,10 +162,10 @@ public class AiModelServiceImpl implements AiModelService { | ||
| 162 | platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); | 162 | platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); |
| 163 | 163 | ||
| 164 | // 创建或获取 VectorStore 对象 | 164 | // 创建或获取 VectorStore 对象 |
| 165 | - // return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); | 165 | + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); |
| 166 | // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); | 166 | // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); |
| 167 | // return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields); | 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 | \ No newline at end of file | 172 | \ No newline at end of file |