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,7 +45,7 @@ public class AiKnowledgeDO extends BaseDO {
45 @TableField(typeHandler = JacksonTypeHandler.class) 45 @TableField(typeHandler = JacksonTypeHandler.class)
46 private List<Long> visibilityPermissions; 46 private List<Long> visibilityPermissions;
47 /** 47 /**
48 - * 嵌入模型编号,高质量模式时维护 48 + * 嵌入模型编号
49 */ 49 */
50 private Long modelId; 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,10 +24,14 @@ public class AiKnowledgeSegmentDO extends BaseDO {
24 * 向量库的编号 24 * 向量库的编号
25 */ 25 */
26 private String vectorId; 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 * 关联 {@link AiKnowledgeDocumentDO#getId()} 35 * 关联 {@link AiKnowledgeDocumentDO#getId()}
32 */ 36 */
33 private Long documentId; 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,24 +6,27 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 11 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  12 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
11 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper; 13 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
12 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; 14 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
13 import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum; 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 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
15 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
16 import org.springframework.ai.document.Document; 20 import org.springframework.ai.document.Document;
17 import org.springframework.ai.reader.tika.TikaDocumentReader; 21 import org.springframework.ai.reader.tika.TikaDocumentReader;
18 import org.springframework.ai.tokenizer.TokenCountEstimator; 22 import org.springframework.ai.tokenizer.TokenCountEstimator;
19 import org.springframework.ai.transformer.splitter.TokenTextSplitter; 23 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
20 -import org.springframework.ai.vectorstore.RedisVectorStore; 24 +import org.springframework.ai.vectorstore.VectorStore;
21 import org.springframework.core.io.ByteArrayResource; 25 import org.springframework.core.io.ByteArrayResource;
22 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
23 import org.springframework.transaction.annotation.Transactional; 27 import org.springframework.transaction.annotation.Transactional;
24 28
25 import java.util.List; 29 import java.util.List;
26 -import java.util.Objects;  
27 30
28 /** 31 /**
29 * AI 知识库-文档 Service 实现类 32 * AI 知识库-文档 Service 实现类
@@ -42,9 +45,14 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -42,9 +45,14 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
42 @Resource 45 @Resource
43 private TokenTextSplitter tokenTextSplitter; 46 private TokenTextSplitter tokenTextSplitter;
44 @Resource 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 @Resource 54 @Resource
47 - private RedisVectorStore vectorStore; 55 + private AiChatModelService chatModelService;
48 56
49 57
50 // TODO 芋艿:需要 review 下,代码格式; 58 // TODO 芋艿:需要 review 下,代码格式;
@@ -53,18 +61,18 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -53,18 +61,18 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
53 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { 61 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
54 // 1.1 下载文档 62 // 1.1 下载文档
55 String url = createReqVO.getUrl(); 63 String url = createReqVO.getUrl();
56 - TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url));  
57 // 1.2 加载文档 64 // 1.2 加载文档
  65 + TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url));
58 List<Document> documents = loader.get(); 66 List<Document> documents = loader.get();
59 Document document = CollUtil.getFirst(documents); 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 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) 73 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
65 .setTokens(tokens).setWordCount(wordCount) 74 .setTokens(tokens).setWordCount(wordCount)
66 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus()); 75 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
67 - // 1.2 文档记录入库  
68 documentMapper.insert(documentDO); 76 documentMapper.insert(documentDO);
69 Long documentId = documentDO.getId(); 77 Long documentId = documentDO.getId();
70 if (CollUtil.isEmpty(documents)) { 78 if (CollUtil.isEmpty(documents)) {
@@ -75,11 +83,16 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -75,11 +83,16 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
75 List<Document> segments = tokenTextSplitter.apply(documents); 83 List<Document> segments = tokenTextSplitter.apply(documents);
76 // 2.2 分段内容入库 84 // 2.2 分段内容入库
77 List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments, 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 .setStatus(CommonStatusEnum.ENABLE.getStatus())); 88 .setStatus(CommonStatusEnum.ENABLE.getStatus()));
81 segmentMapper.insertBatch(segmentDOList); 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 vectorStore.add(segments); 96 vectorStore.add(segments);
84 return documentId; 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 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
2 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO; 3 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
3 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; 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 * AI 知识库-基础信息 Service 接口 8 * AI 知识库-基础信息 Service 接口
@@ -13,7 +15,7 @@ public interface AiKnowledgeService { @@ -13,7 +15,7 @@ public interface AiKnowledgeService {
13 * 创建【我的】知识库 15 * 创建【我的】知识库
14 * 16 *
15 * @param createReqVO 创建信息 17 * @param createReqVO 创建信息
16 - * @param userId 用户编号 18 + * @param userId 用户编号
17 * @return 编号 19 * @return 编号
18 */ 20 */
19 Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId); 21 Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
@@ -23,8 +25,16 @@ public interface AiKnowledgeService { @@ -23,8 +25,16 @@ public interface AiKnowledgeService {
23 * 创建【我的】知识库 25 * 创建【我的】知识库
24 * 26 *
25 * @param updateReqVO 更新信息 27 * @param updateReqVO 更新信息
26 - * @param userId 用户编号 28 + * @param userId 用户编号
27 */ 29 */
28 void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId); 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,7 +29,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
29 private AiChatModelService chatModalService; 29 private AiChatModelService chatModalService;
30 30
31 @Resource 31 @Resource
32 - private AiKnowledgeMapper knowledgeBaseMapper; 32 + private AiKnowledgeMapper knowledgeMapper;
33 33
34 @Override 34 @Override
35 public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { 35 public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) {
@@ -39,7 +39,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { @@ -39,7 +39,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
39 // 2. 插入知识库 39 // 2. 插入知识库
40 AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) 40 AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
41 .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); 41 .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
42 - knowledgeBaseMapper.insert(knowledgeBase); 42 + knowledgeMapper.insert(knowledgeBase);
43 return knowledgeBase.getId(); 43 return knowledgeBase.getId();
44 } 44 }
45 45
@@ -56,11 +56,12 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { @@ -56,11 +56,12 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
56 // 2. 更新知识库 56 // 2. 更新知识库
57 AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); 57 AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
58 updateDO.setModel(model.getModel()); 58 updateDO.setModel(model.getModel());
59 - knowledgeBaseMapper.updateById(updateDO); 59 + knowledgeMapper.updateById(updateDO);
60 } 60 }
61 61
  62 + @Override
62 public AiKnowledgeDO validateKnowledgeExists(Long id) { 63 public AiKnowledgeDO validateKnowledgeExists(Long id) {
63 - AiKnowledgeDO knowledgeBase = knowledgeBaseMapper.selectById(id); 64 + AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
64 if (knowledgeBase == null) { 65 if (knowledgeBase == null) {
65 throw exception(KNOWLEDGE_NOT_EXISTS); 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,7 +9,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 import jakarta.validation.Valid; 10 import jakarta.validation.Valid;
11 import org.springframework.ai.chat.model.ChatModel; 11 import org.springframework.ai.chat.model.ChatModel;
  12 +import org.springframework.ai.embedding.EmbeddingModel;
12 import org.springframework.ai.image.ImageModel; 13 import org.springframework.ai.image.ImageModel;
  14 +import org.springframework.ai.vectorstore.VectorStore;
13 15
14 import java.util.List; 16 import java.util.List;
15 17
@@ -84,6 +86,14 @@ public interface AiApiKeyService { @@ -84,6 +86,14 @@ public interface AiApiKeyService {
84 ChatModel getChatModel(Long id); 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 * 获得 ImageModel 对象 97 * 获得 ImageModel 对象
88 * 98 *
89 * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择 99 * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择
@@ -111,4 +121,12 @@ public interface AiApiKeyService { @@ -111,4 +121,12 @@ public interface AiApiKeyService {
111 */ 121 */
112 SunoApi getSunoApi(); 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 \ No newline at end of file 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,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorFactory;
5 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 6 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
6 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 7 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 8 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; @@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; 14 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
15 import org.springframework.ai.chat.model.ChatModel; 16 import org.springframework.ai.chat.model.ChatModel;
  17 +import org.springframework.ai.embedding.EmbeddingModel;
16 import org.springframework.ai.image.ImageModel; 18 import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.VectorStore;
17 import org.springframework.stereotype.Service; 20 import org.springframework.stereotype.Service;
18 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
19 22
@@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
36 39
37 @Resource 40 @Resource
38 private AiModelFactory modelFactory; 41 private AiModelFactory modelFactory;
  42 + @Resource
  43 + private AiVectorFactory vectorFactory;
39 44
40 @Override 45 @Override
41 public Long createApiKey(AiApiKeySaveReqVO createReqVO) { 46 public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
@@ -105,6 +110,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -105,6 +110,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
105 } 110 }
106 111
107 @Override 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 public ImageModel getImageModel(AiPlatformEnum platform) { 120 public ImageModel getImageModel(AiPlatformEnum platform) {
109 AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); 121 AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
110 if (apiKey == null) { 122 if (apiKey == null) {
@@ -132,4 +144,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -132,4 +144,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
132 } 144 }
133 return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); 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 \ No newline at end of file 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,6 +2,8 @@ package cn.iocoder.yudao.framework.ai.config;
2 2
3 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 3 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; 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 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; 7 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
6 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; 8 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
7 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 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,22 +12,15 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
10 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; 12 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
11 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; 13 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
12 import lombok.extern.slf4j.Slf4j; 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 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; 15 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
17 import org.springframework.ai.tokenizer.TokenCountEstimator; 16 import org.springframework.ai.tokenizer.TokenCountEstimator;
18 import org.springframework.ai.transformer.splitter.TokenTextSplitter; 17 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
19 -import org.springframework.ai.transformers.TransformersEmbeddingModel;  
20 -import org.springframework.ai.vectorstore.RedisVectorStore;  
21 import org.springframework.boot.autoconfigure.AutoConfiguration; 18 import org.springframework.boot.autoconfigure.AutoConfiguration;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
23 -import org.springframework.boot.autoconfigure.data.redis.RedisProperties;  
24 import org.springframework.boot.context.properties.EnableConfigurationProperties; 20 import org.springframework.boot.context.properties.EnableConfigurationProperties;
25 import org.springframework.context.annotation.Bean; 21 import org.springframework.context.annotation.Bean;
26 import org.springframework.context.annotation.Import; 22 import org.springframework.context.annotation.Import;
27 import org.springframework.context.annotation.Lazy; 23 import org.springframework.context.annotation.Lazy;
28 -import redis.clients.jedis.JedisPooled;  
29 24
30 /** 25 /**
31 * 芋道 AI 自动配置 26 * 芋道 AI 自动配置
@@ -43,6 +38,12 @@ public class YudaoAiAutoConfiguration { @@ -43,6 +38,12 @@ public class YudaoAiAutoConfiguration {
43 return new AiModelFactoryImpl(); 38 return new AiModelFactoryImpl();
44 } 39 }
45 40
  41 + @Bean
  42 + public AiVectorFactory aiVectorFactory() {
  43 + return new AiVectorFactoryImpl();
  44 + }
  45 +
  46 +
46 // ========== 各种 AI Client 创建 ========== 47 // ========== 各种 AI Client 创建 ==========
47 48
48 @Bean 49 @Bean
@@ -85,30 +86,31 @@ public class YudaoAiAutoConfiguration { @@ -85,30 +86,31 @@ public class YudaoAiAutoConfiguration {
85 } 86 }
86 87
87 // ========== rag 相关 ========== 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 @Bean 115 @Bean
114 @Lazy // TODO 芋艿:临时注释,避免无法启动 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,6 +4,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 5 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
6 import org.springframework.ai.chat.model.ChatModel; 6 import org.springframework.ai.chat.model.ChatModel;
  7 +import org.springframework.ai.embedding.EmbeddingModel;
7 import org.springframework.ai.image.ImageModel; 8 import org.springframework.ai.image.ImageModel;
8 9
9 /** 10 /**
@@ -26,6 +27,18 @@ public interface AiModelFactory { @@ -26,6 +27,18 @@ public interface AiModelFactory {
26 ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); 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 * 基于默认配置,获得 ChatModel 对象 42 * 基于默认配置,获得 ChatModel 对象
30 * 43 *
31 * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 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,6 +21,7 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
21 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; 21 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
22 import com.alibaba.dashscope.aigc.generation.Generation; 22 import com.alibaba.dashscope.aigc.generation.Generation;
23 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; 23 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
  24 +import com.alibaba.dashscope.embeddings.TextEmbedding;
24 import com.azure.ai.openai.OpenAIClient; 25 import com.azure.ai.openai.OpenAIClient;
25 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; 26 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
26 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; 27 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
@@ -37,6 +38,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; @@ -37,6 +38,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
37 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties; 38 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
38 import org.springframework.ai.azure.openai.AzureOpenAiChatModel; 39 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
39 import org.springframework.ai.chat.model.ChatModel; 40 import org.springframework.ai.chat.model.ChatModel;
  41 +import org.springframework.ai.embedding.EmbeddingModel;
40 import org.springframework.ai.image.ImageModel; 42 import org.springframework.ai.image.ImageModel;
41 import org.springframework.ai.model.function.FunctionCallbackContext; 43 import org.springframework.ai.model.function.FunctionCallbackContext;
42 import org.springframework.ai.ollama.OllamaChatModel; 44 import org.springframework.ai.ollama.OllamaChatModel;
@@ -98,6 +100,21 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -98,6 +100,21 @@ public class AiModelFactoryImpl implements AiModelFactory {
98 } 100 }
99 101
100 @Override 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 public ChatModel getDefaultChatModel(AiPlatformEnum platform) { 118 public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
102 //noinspection EnhancedSwitchMigration 119 //noinspection EnhancedSwitchMigration
103 switch (platform) { 120 switch (platform) {
@@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
239 256
240 /** 257 /**
241 * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel( 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 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { 261 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
245 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); 262 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
249 266
250 /** 267 /**
251 * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel( 268 * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(
252 - * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 269 + *ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
253 */ 270 */
254 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { 271 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
255 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); 272 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
315 return new StabilityAiImageModel(stabilityAiApi); 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,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel;
19 import org.springframework.ai.vectorstore.RedisVectorStore; 19 import org.springframework.ai.vectorstore.RedisVectorStore;
20 import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; 20 import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
21 import org.springframework.boot.autoconfigure.AutoConfiguration; 21 import org.springframework.boot.autoconfigure.AutoConfiguration;
  22 +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 25 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled; @@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled;
38 */ 39 */
39 @AutoConfiguration(after = RedisAutoConfiguration.class) 40 @AutoConfiguration(after = RedisAutoConfiguration.class)
40 @ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class}) 41 @ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
41 -//@ConditionalOnBean(JedisConnectionFactory.class) 42 +@ConditionalOnBean(JedisConnectionFactory.class)
42 @EnableConfigurationProperties(RedisVectorStoreProperties.class) 43 @EnableConfigurationProperties(RedisVectorStoreProperties.class)
43 public class RedisVectorStoreAutoConfiguration { 44 public class RedisVectorStoreAutoConfiguration {
44 45