Commit 92d32b652e05639b6b9407930e9218b75cdbb8f7
1 parent
c26cecae
【新增】AI 知识库: 段落召回
Showing
6 changed files
with
88 additions
and
10 deletions
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 | 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 8 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 9 | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 10 | 12 | /** |
| 11 | 13 | * AI 知识库-分片 Mapper |
| 12 | 14 | * |
| ... | ... | @@ -22,4 +24,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment |
| 22 | 24 | .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword()) |
| 23 | 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 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 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 | 6 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 8 | 9 | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 9 | 12 | /** |
| 10 | 13 | * AI 知识库段落 Service 接口 |
| 11 | 14 | * |
| ... | ... | @@ -35,4 +38,13 @@ public interface AiKnowledgeSegmentService { |
| 35 | 38 | */ |
| 36 | 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 | 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 | 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.segment.AiKnowledgeSegmentPageReqVO; |
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; | |
| 6 | 9 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; |
| 7 | 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 | 12 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 13 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | |
| 9 | 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 | 17 | import jakarta.annotation.Resource; |
| 11 | 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 | 23 | import org.springframework.stereotype.Service; |
| 13 | 24 | |
| 25 | +import java.util.List; | |
| 26 | + | |
| 14 | 27 | /** |
| 15 | 28 | * AI 知识库分片 Service 实现类 |
| 16 | 29 | * |
| ... | ... | @@ -23,6 +36,13 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService |
| 23 | 36 | @Resource |
| 24 | 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 | 46 | @Override |
| 27 | 47 | public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { |
| 28 | 48 | return segmentMapper.selectPage(pageReqVO); |
| ... | ... | @@ -39,4 +59,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService |
| 39 | 59 | segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class)); |
| 40 | 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 | 82 | // TODO @xin 免费版本 |
| 83 | 83 | // @Bean |
| 84 | 84 | // @Lazy // TODO 芋艿:临时注释,避免无法启动」 |
| 85 | -// public EmbeddingModel transformersEmbeddingClient() { | |
| 85 | +// public TransformersEmbeddingModel transformersEmbeddingClient() { | |
| 86 | 86 | // return new TransformersEmbeddingModel(MetadataMode.EMBED); |
| 87 | 87 | // } |
| 88 | 88 | |
| ... | ... | @@ -91,23 +91,24 @@ public class YudaoAiAutoConfiguration { |
| 91 | 91 | */ |
| 92 | 92 | // @Bean |
| 93 | 93 | // @Lazy // TODO 芋艿:临时注释,避免无法启动 |
| 94 | -// public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties, | |
| 94 | +// public RedisVectorStore vectorStore(TransformersEmbeddingModel embeddingModel, RedisVectorStoreProperties properties, | |
| 95 | 95 | // RedisProperties redisProperties) { |
| 96 | 96 | // var config = RedisVectorStore.RedisVectorStoreConfig.builder() |
| 97 | 97 | // .withIndexName(properties.getIndex()) |
| 98 | 98 | // .withPrefix(properties.getPrefix()) |
| 99 | +// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC)) | |
| 99 | 100 | // .build(); |
| 100 | 101 | // |
| 101 | -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel, | |
| 102 | +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, | |
| 102 | 103 | // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), |
| 103 | 104 | // properties.isInitializeSchema()); |
| 104 | 105 | // redisVectorStore.afterPropertiesSet(); |
| 105 | 106 | // return redisVectorStore; |
| 106 | 107 | // } |
| 107 | - | |
| 108 | 108 | @Bean |
| 109 | 109 | @Lazy // TODO 芋艿:临时注释,避免无法启动 |
| 110 | 110 | public TokenTextSplitter tokenTextSplitter() { |
| 111 | + //TODO @xin 配置提取 | |
| 111 | 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 | 66 | import org.springframework.web.client.ResponseErrorHandler; |
| 67 | 67 | import org.springframework.web.client.RestClient; |
| 68 | 68 | import redis.clients.jedis.JedisPooled; |
| 69 | +import redis.clients.jedis.search.Schema; | |
| 69 | 70 | |
| 70 | 71 | import java.util.List; |
| 71 | 72 | |
| ... | ... | @@ -200,14 +201,11 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 200 | 201 | public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { |
| 201 | 202 | String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); |
| 202 | 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 | 205 | var config = RedisVectorStore.RedisVectorStoreConfig.builder() |
| 209 | - .withIndexName(index) | |
| 206 | + .withIndexName(cacheKey) | |
| 210 | 207 | .withPrefix(prefix) |
| 208 | + .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC)) | |
| 211 | 209 | .build(); |
| 212 | 210 | RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); |
| 213 | 211 | RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, | ... | ... |