Commit ed2296e4c772724004d92b0b0dc3b209cd70a78b
1 parent
6cceab5b
【解决todo】AI 知识库
Showing
10 changed files
with
39 additions
and
105 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO; |
| 5 | 5 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; |
| 6 | -import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeBaseService; | |
| 6 | +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; | |
| 7 | 7 | import io.swagger.v3.oas.annotations.Operation; |
| 8 | 8 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 9 | 9 | import jakarta.annotation.Resource; |
| ... | ... | @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti |
| 19 | 19 | public class AiKnowledgeController { |
| 20 | 20 | |
| 21 | 21 | @Resource |
| 22 | - private AiKnowledgeBaseService knowledgeBaseService; | |
| 22 | + private AiKnowledgeService knowledgeBaseService; | |
| 23 | 23 | |
| 24 | 24 | @PostMapping("/create-my") |
| 25 | 25 | @Operation(summary = "创建【我的】知识库") | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeBaseDO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
| ... | ... | @@ -10,15 +10,14 @@ import lombok.Data; |
| 10 | 10 | |
| 11 | 11 | import java.util.List; |
| 12 | 12 | |
| 13 | -// TODO @xin:要不把 AiKnowledgeBaseDO 改成 AiKnowledgeDO。感觉 base 后缀,感觉有点奇怪(让人以为是基类)。然后,我们很多地方的外键编号,都是 knowledgeId | |
| 14 | 13 | /** |
| 15 | 14 | * AI 知识库 DO |
| 16 | 15 | * |
| 17 | 16 | * @author xiaoxin |
| 18 | 17 | */ |
| 19 | -@TableName(value = "ai_knowledge_base", autoResultMap = true) | |
| 18 | +@TableName(value = "ai_knowledge", autoResultMap = true) | |
| 20 | 19 | @Data |
| 21 | -public class AiKnowledgeBaseDO extends BaseDO { | |
| 20 | +public class AiKnowledgeDO extends BaseDO { | |
| 22 | 21 | |
| 23 | 22 | /** |
| 24 | 23 | * 编号 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeBaseMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO; | |
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | |
| 5 | 5 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 6 | |
| 7 | 7 | /** |
| ... | ... | @@ -10,5 +10,5 @@ import org.apache.ibatis.annotations.Mapper; |
| 10 | 10 | * @author xiaoxin |
| 11 | 11 | */ |
| 12 | 12 | @Mapper |
| 13 | -public interface AiKnowledgeBaseMapper extends BaseMapperX<AiKnowledgeBaseDO> { | |
| 13 | +public interface AiKnowledgeBaseMapper extends BaseMapperX<AiKnowledgeDO> { | |
| 14 | 14 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingService.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.knowledge; | |
| 2 | - | |
| 3 | -import org.springframework.ai.document.Document; | |
| 4 | -import org.springframework.ai.vectorstore.SearchRequest; | |
| 5 | - | |
| 6 | -import java.util.List; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * AI 嵌入 Service 接口 | |
| 10 | - * | |
| 11 | - * @author xiaoxin | |
| 12 | - */ | |
| 13 | -public interface AiEmbeddingService { | |
| 14 | - | |
| 15 | - /** | |
| 16 | - * 向量化文档并存储 | |
| 17 | - */ | |
| 18 | - void add(List<Document> documents); | |
| 19 | - | |
| 20 | - /** | |
| 21 | - * 相似查询 | |
| 22 | - * | |
| 23 | - * @param request 查询实体 | |
| 24 | - */ | |
| 25 | - List<Document> similaritySearch(SearchRequest request); | |
| 26 | - | |
| 27 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingServiceImpl.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.knowledge; | |
| 2 | - | |
| 3 | -import jakarta.annotation.Resource; | |
| 4 | -import org.springframework.ai.document.Document; | |
| 5 | -import org.springframework.ai.vectorstore.RedisVectorStore; | |
| 6 | -import org.springframework.ai.vectorstore.SearchRequest; | |
| 7 | -import org.springframework.stereotype.Service; | |
| 8 | - | |
| 9 | -import java.util.List; | |
| 10 | - | |
| 11 | -// TODO @xin:是不是不用 AiEmbeddingServiceImpl,直接 vectorStore 注入到需要的地方就好啦。通过 KnowledgeDocumentService 返回就好。 | |
| 12 | -/** | |
| 13 | - * AI 嵌入 Service 实现类 | |
| 14 | - * | |
| 15 | - * @author xiaoxin | |
| 16 | - */ | |
| 17 | -@Service | |
| 18 | -public class AiEmbeddingServiceImpl implements AiEmbeddingService { | |
| 19 | - | |
| 20 | - @Resource | |
| 21 | - private RedisVectorStore vectorStore; | |
| 22 | - | |
| 23 | - @Override | |
| 24 | -// @Async | |
| 25 | - // TODO xiaoxin 报错先注释 | |
| 26 | - public void add(List<Document> documents) { | |
| 27 | - vectorStore.add(documents); | |
| 28 | - } | |
| 29 | - | |
| 30 | - @Override | |
| 31 | - public List<Document> similaritySearch(SearchRequest request) { | |
| 32 | - return vectorStore.similaritySearch(request); | |
| 33 | - } | |
| 34 | - | |
| 35 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
| ... | ... | @@ -14,8 +14,9 @@ import jakarta.annotation.Resource; |
| 14 | 14 | import lombok.extern.slf4j.Slf4j; |
| 15 | 15 | import org.springframework.ai.document.Document; |
| 16 | 16 | import org.springframework.ai.reader.tika.TikaDocumentReader; |
| 17 | -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; | |
| 17 | +import org.springframework.ai.tokenizer.TokenCountEstimator; | |
| 18 | 18 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
| 19 | +import org.springframework.ai.vectorstore.RedisVectorStore; | |
| 19 | 20 | import org.springframework.beans.factory.annotation.Value; |
| 20 | 21 | import org.springframework.stereotype.Service; |
| 21 | 22 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -39,52 +40,49 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic |
| 39 | 40 | |
| 40 | 41 | @Resource |
| 41 | 42 | private TokenTextSplitter tokenTextSplitter; |
| 42 | - | |
| 43 | 43 | @Resource |
| 44 | - private AiEmbeddingService embeddingService; | |
| 44 | + private TokenCountEstimator TOKEN_COUNT_ESTIMATOR; | |
| 45 | + @Resource | |
| 46 | + private RedisVectorStore vectorStore; | |
| 45 | 47 | |
| 46 | - // TODO @xin:@Resource 注入 | |
| 47 | - private static final JTokkitTokenCountEstimator TOKEN_COUNT_ESTIMATOR = new JTokkitTokenCountEstimator(); | |
| 48 | 48 | |
| 49 | 49 | // TODO xiaoxin 临时测试用,后续删 |
| 50 | 50 | @Value("classpath:/webapp/test/Fel.pdf") |
| 51 | 51 | private org.springframework.core.io.Resource data; |
| 52 | 52 | |
| 53 | 53 | // TODO 芋艿:需要 review 下,代码格式; |
| 54 | - // TODO @xin:最好有 1、/2、/3 这种,让代码更有层次感 | |
| 55 | 54 | @Override |
| 56 | 55 | @Transactional(rollbackFor = Exception.class) |
| 57 | 56 | public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { |
| 58 | 57 | // TODO xiaoxin 后续从 url 加载 |
| 59 | 58 | TikaDocumentReader loader = new TikaDocumentReader(data); |
| 60 | - // 加载文档 | |
| 59 | + // 1.1 加载文档 | |
| 61 | 60 | List<Document> documents = loader.get(); |
| 62 | 61 | Document document = CollUtil.getFirst(documents); |
| 63 | 62 | // TODO @xin:是不是不存在,就抛出异常呀;厚泽 return 呀; |
| 64 | - // TODO 芋艿 文档层面有没有可能会比较大,这两个字段是否可以从分段表计算得出?回复:先直接算; | |
| 65 | 63 | Integer tokens = Objects.nonNull(document) ? TOKEN_COUNT_ESTIMATOR.estimate(document.getContent()) : 0; |
| 66 | 64 | Integer wordCount = Objects.nonNull(document) ? document.getContent().length() : 0; |
| 67 | 65 | |
| 68 | 66 | AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) |
| 69 | 67 | .setTokens(tokens).setWordCount(wordCount) |
| 70 | 68 | .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus()); |
| 71 | - // 文档记录入库 | |
| 69 | + // 1.2 文档记录入库 | |
| 72 | 70 | documentMapper.insert(documentDO); |
| 73 | 71 | Long documentId = documentDO.getId(); |
| 74 | 72 | if (CollUtil.isEmpty(documents)) { |
| 75 | 73 | return documentId; |
| 76 | 74 | } |
| 77 | 75 | |
| 78 | - // 文档分段 | |
| 76 | + // 2.1 文档分段 | |
| 79 | 77 | List<Document> segments = tokenTextSplitter.apply(documents); |
| 80 | - // 分段内容入库 | |
| 78 | + // 2.2 分段内容入库 | |
| 81 | 79 | List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments, |
| 82 | 80 | segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId) |
| 83 | 81 | .setTokens(TOKEN_COUNT_ESTIMATOR.estimate(segment.getContent())).setWordCount(segment.getContent().length()) |
| 84 | 82 | .setStatus(CommonStatusEnum.ENABLE.getStatus())); |
| 85 | 83 | segmentMapper.insertBatch(segmentDOList); |
| 86 | - // 向量化并存储 | |
| 87 | - embeddingService.add(segments); | |
| 84 | + // 3 向量化并存储 | |
| 85 | + vectorStore.add(segments); | |
| 88 | 86 | return documentId; |
| 89 | 87 | } |
| 90 | 88 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeBaseService.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeBaseServiceImpl.java renamed to 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.lang.Assert; | |
| 4 | 3 | import cn.hutool.core.util.ObjUtil; |
| 5 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 7 | 6 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO; |
| 8 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; |
| 9 | -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO; | |
| 8 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | |
| 10 | 9 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 11 | 10 | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeBaseMapper; |
| 12 | 11 | import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; |
| ... | ... | @@ -24,7 +23,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_ |
| 24 | 23 | */ |
| 25 | 24 | @Service |
| 26 | 25 | @Slf4j |
| 27 | -public class AiKnowledgeBaseServiceImpl implements AiKnowledgeBaseService { | |
| 26 | +public class AiKnowledgeServiceImpl implements AiKnowledgeService { | |
| 28 | 27 | |
| 29 | 28 | @Resource |
| 30 | 29 | private AiChatModelService chatModalService; |
| ... | ... | @@ -34,42 +33,34 @@ public class AiKnowledgeBaseServiceImpl implements AiKnowledgeBaseService { |
| 34 | 33 | |
| 35 | 34 | @Override |
| 36 | 35 | public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { |
| 37 | - // TODO @xin:貌似直接调用 chatModalService.validateChatModel(id) 完事,不用搞个方法 | |
| 38 | 36 | // 1. 校验模型配置 |
| 39 | - AiChatModelDO model = validateChatModel(createReqVO.getModelId()); | |
| 37 | + AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId()); | |
| 40 | 38 | |
| 41 | 39 | // 2. 插入知识库 |
| 42 | - // TODO @xin:不用 DO 结尾 | |
| 43 | - AiKnowledgeBaseDO knowledgeBaseDO = BeanUtils.toBean(createReqVO, AiKnowledgeBaseDO.class) | |
| 40 | + AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) | |
| 44 | 41 | .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 45 | - knowledgeBaseMapper.insert(knowledgeBaseDO); | |
| 46 | - return knowledgeBaseDO.getId(); | |
| 42 | + knowledgeBaseMapper.insert(knowledgeBase); | |
| 43 | + return knowledgeBase.getId(); | |
| 47 | 44 | } |
| 48 | 45 | |
| 49 | 46 | @Override |
| 50 | 47 | public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) { |
| 51 | 48 | // 1.1 校验知识库存在 |
| 52 | - AiKnowledgeBaseDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId()); | |
| 49 | + AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId()); | |
| 53 | 50 | if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) { |
| 54 | 51 | throw exception(KNOWLEDGE_NOT_EXISTS); |
| 55 | 52 | } |
| 56 | 53 | // 1.2 校验模型配置 |
| 57 | - AiChatModelDO model = validateChatModel(updateReqVO.getModelId()); | |
| 54 | + AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId()); | |
| 58 | 55 | |
| 59 | 56 | // 2. 更新知识库 |
| 60 | - AiKnowledgeBaseDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeBaseDO.class); | |
| 57 | + AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); | |
| 61 | 58 | updateDO.setModel(model.getModel()); |
| 62 | 59 | knowledgeBaseMapper.updateById(updateDO); |
| 63 | 60 | } |
| 64 | 61 | |
| 65 | - private AiChatModelDO validateChatModel(Long id) { | |
| 66 | - AiChatModelDO model = chatModalService.validateChatModel(id); | |
| 67 | - Assert.notNull(model, "未找到对应嵌入模型"); | |
| 68 | - return model; | |
| 69 | - } | |
| 70 | - | |
| 71 | - public AiKnowledgeBaseDO validateKnowledgeExists(Long id) { | |
| 72 | - AiKnowledgeBaseDO knowledgeBase = knowledgeBaseMapper.selectById(id); | |
| 62 | + public AiKnowledgeDO validateKnowledgeExists(Long id) { | |
| 63 | + AiKnowledgeDO knowledgeBase = knowledgeBaseMapper.selectById(id); | |
| 73 | 64 | if (knowledgeBase == null) { |
| 74 | 65 | throw exception(KNOWLEDGE_NOT_EXISTS); |
| 75 | 66 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| ... | ... | @@ -13,6 +13,8 @@ import lombok.extern.slf4j.Slf4j; |
| 13 | 13 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; |
| 14 | 14 | import org.springframework.ai.document.MetadataMode; |
| 15 | 15 | import org.springframework.ai.embedding.EmbeddingModel; |
| 16 | +import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; | |
| 17 | +import org.springframework.ai.tokenizer.TokenCountEstimator; | |
| 16 | 18 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
| 17 | 19 | import org.springframework.ai.transformers.TransformersEmbeddingModel; |
| 18 | 20 | import org.springframework.ai.vectorstore.RedisVectorStore; |
| ... | ... | @@ -90,7 +92,7 @@ public class YudaoAiAutoConfiguration { |
| 90 | 92 | } |
| 91 | 93 | |
| 92 | 94 | /** |
| 93 | - * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑 | |
| 95 | + * TODO @xin 抽离出去,根据具体模型走 | |
| 94 | 96 | */ |
| 95 | 97 | @Bean |
| 96 | 98 | @Lazy // TODO 芋艿:临时注释,避免无法启动 |
| ... | ... | @@ -114,4 +116,10 @@ public class YudaoAiAutoConfiguration { |
| 114 | 116 | return new TokenTextSplitter(500, 100, 5, 10000, true); |
| 115 | 117 | } |
| 116 | 118 | |
| 119 | + @Bean | |
| 120 | + @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 121 | + public TokenCountEstimator tokenCountEstimator() { | |
| 122 | + return new JTokkitTokenCountEstimator(); | |
| 123 | + } | |
| 124 | + | |
| 117 | 125 | } |
| 118 | 126 | \ No newline at end of file | ... | ... |