Commit f97fb0a8fe9e7987233e0f81940b3521544f2031
1 parent
024109da
【新增】AI 知识库: AiVectorFactory 负责管理不同 EmbeddingModel 对应的 VectorStore
Showing
13 changed files
with
239 additions
and
52 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
| ... | ... | @@ -24,10 +24,14 @@ public class AiKnowledgeSegmentDO extends BaseDO { |
| 24 | 24 | * 向量库的编号 |
| 25 | 25 | */ |
| 26 | 26 | private String vectorId; |
| 27 | - // TODO @新:knowledgeId 加个,会方便点 | |
| 27 | + /** | |
| 28 | + * 知识库编号 | |
| 29 | + * 关联 {@link AiKnowledgeDO#getId()} | |
| 30 | + */ | |
| 31 | + private Long knowledgeId; | |
| 28 | 32 | /** |
| 29 | 33 | * 文档编号 |
| 30 | - * | |
| 34 | + * <p> | |
| 31 | 35 | * 关联 {@link AiKnowledgeDocumentDO#getId()} |
| 32 | 36 | */ |
| 33 | 37 | private Long documentId; | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
| ... | ... | @@ -6,24 +6,27 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 8 | 8 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO; |
| 9 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | |
| 9 | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| 10 | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 12 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | |
| 11 | 13 | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper; |
| 12 | 14 | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; |
| 13 | 15 | import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum; |
| 16 | +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; | |
| 17 | +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; | |
| 14 | 18 | import jakarta.annotation.Resource; |
| 15 | 19 | import lombok.extern.slf4j.Slf4j; |
| 16 | 20 | import org.springframework.ai.document.Document; |
| 17 | 21 | import org.springframework.ai.reader.tika.TikaDocumentReader; |
| 18 | 22 | import org.springframework.ai.tokenizer.TokenCountEstimator; |
| 19 | 23 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
| 20 | -import org.springframework.ai.vectorstore.RedisVectorStore; | |
| 24 | +import org.springframework.ai.vectorstore.VectorStore; | |
| 21 | 25 | import org.springframework.core.io.ByteArrayResource; |
| 22 | 26 | import org.springframework.stereotype.Service; |
| 23 | 27 | import org.springframework.transaction.annotation.Transactional; |
| 24 | 28 | |
| 25 | 29 | import java.util.List; |
| 26 | -import java.util.Objects; | |
| 27 | 30 | |
| 28 | 31 | /** |
| 29 | 32 | * AI 知识库-文档 Service 实现类 |
| ... | ... | @@ -42,9 +45,14 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic |
| 42 | 45 | @Resource |
| 43 | 46 | private TokenTextSplitter tokenTextSplitter; |
| 44 | 47 | @Resource |
| 45 | - private TokenCountEstimator TOKEN_COUNT_ESTIMATOR; | |
| 48 | + private TokenCountEstimator tokenCountEstimator; | |
| 49 | + | |
| 50 | + @Resource | |
| 51 | + private AiApiKeyService apiKeyService; | |
| 52 | + @Resource | |
| 53 | + private AiKnowledgeService knowledgeService; | |
| 46 | 54 | @Resource |
| 47 | - private RedisVectorStore vectorStore; | |
| 55 | + private AiChatModelService chatModelService; | |
| 48 | 56 | |
| 49 | 57 | |
| 50 | 58 | // TODO 芋艿:需要 review 下,代码格式; |
| ... | ... | @@ -53,18 +61,18 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic |
| 53 | 61 | public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { |
| 54 | 62 | // 1.1 下载文档 |
| 55 | 63 | String url = createReqVO.getUrl(); |
| 56 | - TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url)); | |
| 57 | 64 | // 1.2 加载文档 |
| 65 | + TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url)); | |
| 58 | 66 | List<Document> documents = loader.get(); |
| 59 | 67 | Document document = CollUtil.getFirst(documents); |
| 60 | - // TODO @xin:是不是不存在,就抛出异常呀;厚泽 return 呀; | |
| 61 | - Integer tokens = Objects.nonNull(document) ? TOKEN_COUNT_ESTIMATOR.estimate(document.getContent()) : 0; | |
| 62 | - Integer wordCount = Objects.nonNull(document) ? document.getContent().length() : 0; | |
| 68 | + String content = document.getContent(); | |
| 69 | + Integer tokens = tokenCountEstimator.estimate(content); | |
| 70 | + Integer wordCount = content.length(); | |
| 63 | 71 | |
| 72 | + // 1.3 文档记录入库 | |
| 64 | 73 | AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) |
| 65 | 74 | .setTokens(tokens).setWordCount(wordCount) |
| 66 | 75 | .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus()); |
| 67 | - // 1.2 文档记录入库 | |
| 68 | 76 | documentMapper.insert(documentDO); |
| 69 | 77 | Long documentId = documentDO.getId(); |
| 70 | 78 | if (CollUtil.isEmpty(documents)) { |
| ... | ... | @@ -75,11 +83,16 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic |
| 75 | 83 | List<Document> segments = tokenTextSplitter.apply(documents); |
| 76 | 84 | // 2.2 分段内容入库 |
| 77 | 85 | List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments, |
| 78 | - segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId) | |
| 79 | - .setTokens(TOKEN_COUNT_ESTIMATOR.estimate(segment.getContent())).setWordCount(segment.getContent().length()) | |
| 86 | + segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId).setKnowledgeId(createReqVO.getKnowledgeId()) | |
| 87 | + .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length()) | |
| 80 | 88 | .setStatus(CommonStatusEnum.ENABLE.getStatus())); |
| 81 | 89 | segmentMapper.insertBatch(segmentDOList); |
| 82 | - // 3 向量化并存储 | |
| 90 | + | |
| 91 | + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId()); | |
| 92 | + AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); | |
| 93 | + // 3.1 获取向量存储实例 | |
| 94 | + VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId()); | |
| 95 | + // 3.2 向量化并存储 | |
| 83 | 96 | vectorStore.add(segments); |
| 84 | 97 | return documentId; |
| 85 | 98 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service.knowledge; |
| 2 | + | |
| 2 | 3 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO; |
| 3 | 4 | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; |
| 5 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | |
| 4 | 6 | |
| 5 | 7 | /** |
| 6 | 8 | * AI 知识库-基础信息 Service 接口 |
| ... | ... | @@ -13,7 +15,7 @@ public interface AiKnowledgeService { |
| 13 | 15 | * 创建【我的】知识库 |
| 14 | 16 | * |
| 15 | 17 | * @param createReqVO 创建信息 |
| 16 | - * @param userId 用户编号 | |
| 18 | + * @param userId 用户编号 | |
| 17 | 19 | * @return 编号 |
| 18 | 20 | */ |
| 19 | 21 | Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId); |
| ... | ... | @@ -23,8 +25,16 @@ public interface AiKnowledgeService { |
| 23 | 25 | * 创建【我的】知识库 |
| 24 | 26 | * |
| 25 | 27 | * @param updateReqVO 更新信息 |
| 26 | - * @param userId 用户编号 | |
| 28 | + * @param userId 用户编号 | |
| 27 | 29 | */ |
| 28 | 30 | void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId); |
| 29 | 31 | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 校验知识库是否存在 | |
| 35 | + * | |
| 36 | + * @param id 记录编号 | |
| 37 | + */ | |
| 38 | + AiKnowledgeDO validateKnowledgeExists(Long id); | |
| 39 | + | |
| 30 | 40 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
| ... | ... | @@ -29,7 +29,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { |
| 29 | 29 | private AiChatModelService chatModalService; |
| 30 | 30 | |
| 31 | 31 | @Resource |
| 32 | - private AiKnowledgeMapper knowledgeBaseMapper; | |
| 32 | + private AiKnowledgeMapper knowledgeMapper; | |
| 33 | 33 | |
| 34 | 34 | @Override |
| 35 | 35 | public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { |
| ... | ... | @@ -39,7 +39,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { |
| 39 | 39 | // 2. 插入知识库 |
| 40 | 40 | AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) |
| 41 | 41 | .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 42 | - knowledgeBaseMapper.insert(knowledgeBase); | |
| 42 | + knowledgeMapper.insert(knowledgeBase); | |
| 43 | 43 | return knowledgeBase.getId(); |
| 44 | 44 | } |
| 45 | 45 | |
| ... | ... | @@ -56,11 +56,12 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { |
| 56 | 56 | // 2. 更新知识库 |
| 57 | 57 | AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); |
| 58 | 58 | updateDO.setModel(model.getModel()); |
| 59 | - knowledgeBaseMapper.updateById(updateDO); | |
| 59 | + knowledgeMapper.updateById(updateDO); | |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | + @Override | |
| 62 | 63 | public AiKnowledgeDO validateKnowledgeExists(Long id) { |
| 63 | - AiKnowledgeDO knowledgeBase = knowledgeBaseMapper.selectById(id); | |
| 64 | + AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id); | |
| 64 | 65 | if (knowledgeBase == null) { |
| 65 | 66 | throw exception(KNOWLEDGE_NOT_EXISTS); |
| 66 | 67 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
| ... | ... | @@ -9,7 +9,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR |
| 9 | 9 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| 10 | 10 | import jakarta.validation.Valid; |
| 11 | 11 | import org.springframework.ai.chat.model.ChatModel; |
| 12 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 12 | 13 | import org.springframework.ai.image.ImageModel; |
| 14 | +import org.springframework.ai.vectorstore.VectorStore; | |
| 13 | 15 | |
| 14 | 16 | import java.util.List; |
| 15 | 17 | |
| ... | ... | @@ -84,6 +86,14 @@ public interface AiApiKeyService { |
| 84 | 86 | ChatModel getChatModel(Long id); |
| 85 | 87 | |
| 86 | 88 | /** |
| 89 | + * 获得 EmbeddingModel 对象 | |
| 90 | + * | |
| 91 | + * @param id 编号 | |
| 92 | + * @return EmbeddingModel 对象 | |
| 93 | + */ | |
| 94 | + EmbeddingModel getEmbeddingModel(Long id); | |
| 95 | + | |
| 96 | + /** | |
| 87 | 97 | * 获得 ImageModel 对象 |
| 88 | 98 | * |
| 89 | 99 | * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择 |
| ... | ... | @@ -111,4 +121,12 @@ public interface AiApiKeyService { |
| 111 | 121 | */ |
| 112 | 122 | SunoApi getSunoApi(); |
| 113 | 123 | |
| 124 | + /** | |
| 125 | + * 获得 vector 对象 | |
| 126 | + * | |
| 127 | + * @param id 编号 | |
| 128 | + * @return VectorStore 对象 | |
| 129 | + */ | |
| 130 | + VectorStore getOrCreateVectorStore(Long id); | |
| 131 | + | |
| 114 | 132 | } |
| 115 | 133 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; |
| 5 | +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorFactory; | |
| 5 | 6 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 6 | 7 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| ... | ... | @@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| 13 | 14 | import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; |
| 14 | 15 | import jakarta.annotation.Resource; |
| 15 | 16 | import org.springframework.ai.chat.model.ChatModel; |
| 17 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 16 | 18 | import org.springframework.ai.image.ImageModel; |
| 19 | +import org.springframework.ai.vectorstore.VectorStore; | |
| 17 | 20 | import org.springframework.stereotype.Service; |
| 18 | 21 | import org.springframework.validation.annotation.Validated; |
| 19 | 22 | |
| ... | ... | @@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { |
| 36 | 39 | |
| 37 | 40 | @Resource |
| 38 | 41 | private AiModelFactory modelFactory; |
| 42 | + @Resource | |
| 43 | + private AiVectorFactory vectorFactory; | |
| 39 | 44 | |
| 40 | 45 | @Override |
| 41 | 46 | public Long createApiKey(AiApiKeySaveReqVO createReqVO) { |
| ... | ... | @@ -105,6 +110,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { |
| 105 | 110 | } |
| 106 | 111 | |
| 107 | 112 | @Override |
| 113 | + public EmbeddingModel getEmbeddingModel(Long id) { | |
| 114 | + AiApiKeyDO apiKey = validateApiKey(id); | |
| 115 | + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); | |
| 116 | + return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl()); | |
| 117 | + } | |
| 118 | + | |
| 119 | + @Override | |
| 108 | 120 | public ImageModel getImageModel(AiPlatformEnum platform) { |
| 109 | 121 | AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); |
| 110 | 122 | if (apiKey == null) { |
| ... | ... | @@ -132,4 +144,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { |
| 132 | 144 | } |
| 133 | 145 | return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); |
| 134 | 146 | } |
| 147 | + | |
| 148 | + @Override | |
| 149 | + public VectorStore getOrCreateVectorStore(Long id) { | |
| 150 | + AiApiKeyDO apiKey = validateApiKey(id); | |
| 151 | + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); | |
| 152 | + return vectorFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl()); | |
| 153 | + } | |
| 135 | 154 | } |
| 136 | 155 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| ... | ... | @@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.ai.config; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; |
| 5 | +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorFactory; | |
| 6 | +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorFactoryImpl; | |
| 5 | 7 | import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; |
| 6 | 8 | import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; |
| 7 | 9 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| ... | ... | @@ -10,22 +12,15 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 10 | 12 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; |
| 11 | 13 | import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; |
| 12 | 14 | import lombok.extern.slf4j.Slf4j; |
| 13 | -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; | |
| 14 | -import org.springframework.ai.document.MetadataMode; | |
| 15 | -import org.springframework.ai.embedding.EmbeddingModel; | |
| 16 | 15 | import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; |
| 17 | 16 | import org.springframework.ai.tokenizer.TokenCountEstimator; |
| 18 | 17 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
| 19 | -import org.springframework.ai.transformers.TransformersEmbeddingModel; | |
| 20 | -import org.springframework.ai.vectorstore.RedisVectorStore; | |
| 21 | 18 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| 22 | 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 23 | -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | |
| 24 | 20 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| 25 | 21 | import org.springframework.context.annotation.Bean; |
| 26 | 22 | import org.springframework.context.annotation.Import; |
| 27 | 23 | import org.springframework.context.annotation.Lazy; |
| 28 | -import redis.clients.jedis.JedisPooled; | |
| 29 | 24 | |
| 30 | 25 | /** |
| 31 | 26 | * 芋道 AI 自动配置 |
| ... | ... | @@ -43,6 +38,12 @@ public class YudaoAiAutoConfiguration { |
| 43 | 38 | return new AiModelFactoryImpl(); |
| 44 | 39 | } |
| 45 | 40 | |
| 41 | + @Bean | |
| 42 | + public AiVectorFactory aiVectorFactory() { | |
| 43 | + return new AiVectorFactoryImpl(); | |
| 44 | + } | |
| 45 | + | |
| 46 | + | |
| 46 | 47 | // ========== 各种 AI Client 创建 ========== |
| 47 | 48 | |
| 48 | 49 | @Bean |
| ... | ... | @@ -85,30 +86,31 @@ public class YudaoAiAutoConfiguration { |
| 85 | 86 | } |
| 86 | 87 | |
| 87 | 88 | // ========== rag 相关 ========== |
| 88 | - @Bean | |
| 89 | - @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 90 | - public EmbeddingModel transformersEmbeddingClient() { | |
| 91 | - return new TransformersEmbeddingModel(MetadataMode.EMBED); | |
| 92 | - } | |
| 89 | + // TODO @xin 免费版本 | |
| 90 | +// @Bean | |
| 91 | +// @Lazy // TODO 芋艿:临时注释,避免无法启动」 | |
| 92 | +// public EmbeddingModel transformersEmbeddingClient() { | |
| 93 | +// return new TransformersEmbeddingModel(MetadataMode.EMBED); | |
| 94 | +// } | |
| 93 | 95 | |
| 94 | 96 | /** |
| 95 | - * TODO @xin 抽离出去,根据具体模型走 | |
| 97 | + * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel | |
| 96 | 98 | */ |
| 97 | - @Bean | |
| 98 | - @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 99 | - public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties, | |
| 100 | - RedisProperties redisProperties) { | |
| 101 | - var config = RedisVectorStore.RedisVectorStoreConfig.builder() | |
| 102 | - .withIndexName(properties.getIndex()) | |
| 103 | - .withPrefix(properties.getPrefix()) | |
| 104 | - .build(); | |
| 105 | - | |
| 106 | - RedisVectorStore redisVectorStore = new RedisVectorStore(config, transformersEmbeddingModel, | |
| 107 | - new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), | |
| 108 | - properties.isInitializeSchema()); | |
| 109 | - redisVectorStore.afterPropertiesSet(); | |
| 110 | - return redisVectorStore; | |
| 111 | - } | |
| 99 | +// @Bean | |
| 100 | +// @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 101 | +// public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties, | |
| 102 | +// RedisProperties redisProperties) { | |
| 103 | +// var config = RedisVectorStore.RedisVectorStoreConfig.builder() | |
| 104 | +// .withIndexName(properties.getIndex()) | |
| 105 | +// .withPrefix(properties.getPrefix()) | |
| 106 | +// .build(); | |
| 107 | +// | |
| 108 | +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel, | |
| 109 | +// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), | |
| 110 | +// properties.isInitializeSchema()); | |
| 111 | +// redisVectorStore.afterPropertiesSet(); | |
| 112 | +// return redisVectorStore; | |
| 113 | +// } | |
| 112 | 114 | |
| 113 | 115 | @Bean |
| 114 | 116 | @Lazy // TODO 芋艿:临时注释,避免无法启动 | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
| ... | ... | @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 5 | 5 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 6 | 6 | import org.springframework.ai.chat.model.ChatModel; |
| 7 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 7 | 8 | import org.springframework.ai.image.ImageModel; |
| 8 | 9 | |
| 9 | 10 | /** |
| ... | ... | @@ -26,6 +27,18 @@ public interface AiModelFactory { |
| 26 | 27 | ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); |
| 27 | 28 | |
| 28 | 29 | /** |
| 30 | + * 基于指定配置,获得 EmbeddingModel 对象 | |
| 31 | + * <p> | |
| 32 | + * 如果不存在,则进行创建 | |
| 33 | + * | |
| 34 | + * @param platform 平台 | |
| 35 | + * @param apiKey API KEY | |
| 36 | + * @param url API URL | |
| 37 | + * @return ChatModel 对象 | |
| 38 | + */ | |
| 39 | + EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url); | |
| 40 | + | |
| 41 | + /** | |
| 29 | 42 | * 基于默认配置,获得 ChatModel 对象 |
| 30 | 43 | * |
| 31 | 44 | * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
| ... | ... | @@ -21,6 +21,7 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel; |
| 21 | 21 | import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; |
| 22 | 22 | import com.alibaba.dashscope.aigc.generation.Generation; |
| 23 | 23 | import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; |
| 24 | +import com.alibaba.dashscope.embeddings.TextEmbedding; | |
| 24 | 25 | import com.azure.ai.openai.OpenAIClient; |
| 25 | 26 | import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; |
| 26 | 27 | import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; |
| ... | ... | @@ -37,6 +38,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; |
| 37 | 38 | import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties; |
| 38 | 39 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; |
| 39 | 40 | import org.springframework.ai.chat.model.ChatModel; |
| 41 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 40 | 42 | import org.springframework.ai.image.ImageModel; |
| 41 | 43 | import org.springframework.ai.model.function.FunctionCallbackContext; |
| 42 | 44 | import org.springframework.ai.ollama.OllamaChatModel; |
| ... | ... | @@ -98,6 +100,21 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 98 | 100 | } |
| 99 | 101 | |
| 100 | 102 | @Override |
| 103 | + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url) { | |
| 104 | + String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url); | |
| 105 | + return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { | |
| 106 | + // TODO @xin 先测试一个 | |
| 107 | + switch (platform) { | |
| 108 | + case TONG_YI: | |
| 109 | + return buildTongYiEmbeddingModel(apiKey); | |
| 110 | + default: | |
| 111 | + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); | |
| 112 | + } | |
| 113 | + }); | |
| 114 | + } | |
| 115 | + | |
| 116 | + | |
| 117 | + @Override | |
| 101 | 118 | public ChatModel getDefaultChatModel(AiPlatformEnum platform) { |
| 102 | 119 | //noinspection EnhancedSwitchMigration |
| 103 | 120 | switch (platform) { |
| ... | ... | @@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 239 | 256 | |
| 240 | 257 | /** |
| 241 | 258 | * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel( |
| 242 | - * ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)} | |
| 259 | + *ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)} | |
| 243 | 260 | */ |
| 244 | 261 | private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { |
| 245 | 262 | url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); |
| ... | ... | @@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 249 | 266 | |
| 250 | 267 | /** |
| 251 | 268 | * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel( |
| 252 | - * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} | |
| 269 | + *ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} | |
| 253 | 270 | */ |
| 254 | 271 | private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { |
| 255 | 272 | url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); |
| ... | ... | @@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 315 | 332 | return new StabilityAiImageModel(stabilityAiApi); |
| 316 | 333 | } |
| 317 | 334 | |
| 335 | + // ========== 各种创建 EmbeddingModel 的方法 ========== | |
| 336 | + | |
| 337 | + /** | |
| 338 | + * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)} | |
| 339 | + */ | |
| 340 | + private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) { | |
| 341 | + TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties(); | |
| 342 | + connectionProperties.setApiKey(apiKey); | |
| 343 | + return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties); | |
| 344 | + } | |
| 345 | + | |
| 318 | 346 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorFactory.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.core.factory; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 4 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 5 | +import org.springframework.ai.vectorstore.VectorStore; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * AI Vector 模型工厂的接口类 | |
| 9 | + * @author xiaoxin | |
| 10 | + */ | |
| 11 | +public interface AiVectorFactory { | |
| 12 | + | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 基于指定配置,获得 VectorStore 对象 | |
| 16 | + * <p> | |
| 17 | + * 如果不存在,则进行创建 | |
| 18 | + * | |
| 19 | + * @param embeddingModel 嵌入模型 | |
| 20 | + * @param platform 平台 | |
| 21 | + * @param apiKey API KEY | |
| 22 | + * @param url API URL | |
| 23 | + * @return VectorStore 对象 | |
| 24 | + */ | |
| 25 | + VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url); | |
| 26 | + | |
| 27 | +} | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorFactoryImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.core.factory; | |
| 2 | + | |
| 3 | +import cn.hutool.core.lang.Singleton; | |
| 4 | +import cn.hutool.core.lang.func.Func0; | |
| 5 | +import cn.hutool.core.util.ArrayUtil; | |
| 6 | +import cn.hutool.core.util.StrUtil; | |
| 7 | +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; | |
| 9 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 10 | +import org.springframework.ai.vectorstore.RedisVectorStore; | |
| 11 | +import org.springframework.ai.vectorstore.VectorStore; | |
| 12 | +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | |
| 13 | +import redis.clients.jedis.JedisPooled; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * AI Vector 模型工厂的实现类 | |
| 17 | + * 使用 redisVectorStore 实现 VectorStore | |
| 18 | + * | |
| 19 | + * @author xiaoxin | |
| 20 | + */ | |
| 21 | +public class AiVectorFactoryImpl implements AiVectorFactory { | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { | |
| 25 | + String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); | |
| 26 | + return Singleton.get(cacheKey, (Func0<VectorStore>) () -> { | |
| 27 | + // TODO 芋艿 @xin 这两个配置取哪好呢 | |
| 28 | + // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上 | |
| 29 | + String index = "default-index"; | |
| 30 | + String prefix = "default:"; | |
| 31 | + var config = RedisVectorStore.RedisVectorStoreConfig.builder() | |
| 32 | + .withIndexName(index) | |
| 33 | + .withPrefix(prefix) | |
| 34 | + .build(); | |
| 35 | + RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); | |
| 36 | + RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, | |
| 37 | + new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), | |
| 38 | + true); | |
| 39 | + redisVectorStore.afterPropertiesSet(); | |
| 40 | + return redisVectorStore; | |
| 41 | + }); | |
| 42 | + } | |
| 43 | + | |
| 44 | + | |
| 45 | + private static String buildClientCacheKey(Class<?> clazz, Object... params) { | |
| 46 | + if (ArrayUtil.isEmpty(params)) { | |
| 47 | + return clazz.getName(); | |
| 48 | + } | |
| 49 | + return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); | |
| 50 | + } | |
| 51 | +} | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java
| ... | ... | @@ -19,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel; |
| 19 | 19 | import org.springframework.ai.vectorstore.RedisVectorStore; |
| 20 | 20 | import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; |
| 21 | 21 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| 22 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | |
| 22 | 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
| 23 | 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
| 24 | 25 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; |
| ... | ... | @@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled; |
| 38 | 39 | */ |
| 39 | 40 | @AutoConfiguration(after = RedisAutoConfiguration.class) |
| 40 | 41 | @ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class}) |
| 41 | -//@ConditionalOnBean(JedisConnectionFactory.class) | |
| 42 | +@ConditionalOnBean(JedisConnectionFactory.class) | |
| 42 | 43 | @EnableConfigurationProperties(RedisVectorStoreProperties.class) |
| 43 | 44 | public class RedisVectorStoreAutoConfiguration { |
| 44 | 45 | ... | ... |