Commit f97fb0a8fe9e7987233e0f81940b3521544f2031

Authored by xiaoxin
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
... ... @@ -45,7 +45,7 @@ public class AiKnowledgeDO extends BaseDO {
45 45 @TableField(typeHandler = JacksonTypeHandler.class)
46 46 private List<Long> visibilityPermissions;
47 47 /**
48   - * 嵌入模型编号,高质量模式时维护
  48 + * 嵌入模型编号
49 49 */
50 50 private Long modelId;
51 51 /**
... ...
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  
... ...