Commit 44bcc9476d7a3a1cdbebfab206640b2dac470c48

Authored by YunaiV
1 parent 6ccd0ca6

【功能新增】AI:增加 QdrantVectorStore 向量库的接入

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
... ... @@ -54,7 +54,6 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
54 54 private AiKnowledgeService knowledgeService;
55 55  
56 56 @Override
57   - @Transactional(rollbackFor = Exception.class)
58 57 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
59 58 // 1. 校验参数
60 59 knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
... ... @@ -74,7 +73,6 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
74 73 }
75 74  
76 75 @Override
77   - @Transactional(rollbackFor = Exception.class)
78 76 public List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO) {
79 77 // 1. 校验参数
80 78 knowledgeService.validateKnowledgeExists(createListReqVO.getKnowledgeId());
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
... ... @@ -115,6 +115,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
115 115 segmentMapper.updateById(newSegment);
116 116 // 3.2 重新向量化,必须开启状态
117 117 if (CommonStatusEnum.isEnable(oldSegment.getStatus())) {
  118 + newSegment.setKnowledgeId(oldSegment.getKnowledgeId()).setDocumentId(oldSegment.getDocumentId());
118 119 writeVectorStore(vectorStore, newSegment, new Document(newSegment.getContent()));
119 120 }
120 121 }
... ... @@ -156,9 +157,10 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
156 157  
157 158 private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
158 159 // 1. 向量存储
159   - segment.getMetadata().put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId());
160   - segment.getMetadata().put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId());
161   - segment.getMetadata().put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId());
  160 + // 为什么要 toString 呢?因为部分 VectorStore 实现,不支持 Long 类型,例如说 QdrantVectorStore
  161 + segment.getMetadata().put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId().toString());
  162 + segment.getMetadata().put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId().toString());
  163 + segment.getMetadata().put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId().toString());
162 164 vectorStore.add(List.of(segment));
163 165  
164 166 // 2. 更新向量 ID
... ... @@ -190,7 +192,8 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
190 192 .similarityThreshold(
191 193 ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold()))
192 194 .filterExpression(new FilterExpressionBuilder()
193   - .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId()).build())
  195 + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId().toString())
  196 + .build())
194 197 .build());
195 198 if (CollUtil.isEmpty(documents)) {
196 199 return ListUtil.empty();
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
... ... @@ -16,8 +16,8 @@ import jakarta.annotation.Resource;
16 16 import org.springframework.ai.chat.model.ChatModel;
17 17 import org.springframework.ai.embedding.EmbeddingModel;
18 18 import org.springframework.ai.image.ImageModel;
19   -import org.springframework.ai.vectorstore.SimpleVectorStore;
20 19 import org.springframework.ai.vectorstore.VectorStore;
  20 +import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
21 21 import org.springframework.stereotype.Service;
22 22 import org.springframework.validation.annotation.Validated;
23 23  
... ... @@ -162,7 +162,8 @@ public class AiModelServiceImpl implements AiModelService {
162 162 platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel());
163 163  
164 164 // 创建或获取 VectorStore 对象
165   - return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel);
  165 +// return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel);
  166 + return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel);
166 167 }
167 168  
168 169 }
169 170 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
... ... @@ -70,6 +70,7 @@
70 70 <groupId>${spring-ai.groupId}</groupId>
71 71 <artifactId>spring-ai-qdrant-store</artifactId>
72 72 <version>${spring-ai.version}</version>
  73 +<!-- <optional>true</optional>-->
73 74 </dependency>
74 75  
75 76 <dependency>
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel
11 11 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
12 12 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
13 13 import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
14 15 import org.springframework.ai.openai.OpenAiChatModel;
15 16 import org.springframework.ai.openai.OpenAiChatOptions;
16 17 import org.springframework.ai.openai.api.OpenAiApi;
... ... @@ -29,7 +30,9 @@ import org.springframework.context.annotation.Lazy;
29 30 * @author fansili
30 31 */
31 32 @AutoConfiguration
32   -@EnableConfigurationProperties(YudaoAiProperties.class)
  33 +@EnableConfigurationProperties({YudaoAiProperties.class,
  34 + QdrantVectorStoreProperties.class // 解析 Qdrant 配置
  35 +})
33 36 @Slf4j
34 37 public class YudaoAiAutoConfiguration {
35 38  
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.lang.Singleton;
6 6 import cn.hutool.core.lang.func.Func0;
7 7 import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.ReflectUtil;
8 9 import cn.hutool.core.util.RuntimeUtil;
9 10 import cn.hutool.core.util.StrUtil;
10 11 import cn.hutool.extra.spring.SpringUtil;
... ... @@ -26,6 +27,9 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
26 27 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
27 28 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
28 29 import com.azure.ai.openai.OpenAIClientBuilder;
  30 +import io.micrometer.observation.ObservationRegistry;
  31 +import io.qdrant.client.QdrantClient;
  32 +import io.qdrant.client.QdrantGrpcClient;
29 33 import lombok.SneakyThrows;
30 34 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
31 35 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
... ... @@ -33,11 +37,14 @@ import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionPr
33 37 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
34 38 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
35 39 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
  40 +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration;
  41 +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
36 42 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
37 43 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
38 44 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
39 45 import org.springframework.ai.chat.model.ChatModel;
40 46 import org.springframework.ai.document.MetadataMode;
  47 +import org.springframework.ai.embedding.BatchingStrategy;
41 48 import org.springframework.ai.embedding.EmbeddingModel;
42 49 import org.springframework.ai.image.ImageModel;
43 50 import org.springframework.ai.ollama.OllamaChatModel;
... ... @@ -57,10 +64,15 @@ import org.springframework.ai.stabilityai.StabilityAiImageModel;
57 64 import org.springframework.ai.stabilityai.api.StabilityAiApi;
58 65 import org.springframework.ai.vectorstore.SimpleVectorStore;
59 66 import org.springframework.ai.vectorstore.VectorStore;
  67 +import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
  68 +import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
  69 +import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
60 70 import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
61 71 import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
62 72 import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
63 73 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
  74 +import org.springframework.beans.BeansException;
  75 +import org.springframework.beans.factory.ObjectProvider;
64 76 import org.springframework.web.client.RestClient;
65 77  
66 78 import java.io.File;
... ... @@ -214,13 +226,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
214 226  
215 227 @Override
216 228 public VectorStore getOrCreateVectorStore(Class<? extends VectorStore> type, EmbeddingModel embeddingModel) {
217   - // String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey,
218   - // url);
219 229 String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel, type);
220 230 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
221 231 if (type == SimpleVectorStore.class) {
222 232 return buildSimpleVectorStore(embeddingModel);
223 233 }
  234 + if (type == QdrantVectorStore.class) {
  235 + return buildQdrantVectorStore(embeddingModel);
  236 + }
224 237 throw new IllegalArgumentException(StrUtil.format("未知类型({})", type));
225 238 // TODO @芋艿:先临时使用 store
226 239 // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的
... ... @@ -456,4 +469,38 @@ public class AiModelFactoryImpl implements AiModelFactory {
456 469 return vectorStore;
457 470 }
458 471  
  472 + private QdrantVectorStore buildQdrantVectorStore(EmbeddingModel embeddingModel) {
  473 + QdrantVectorStoreAutoConfiguration configuration = new QdrantVectorStoreAutoConfiguration();
  474 + QdrantVectorStoreProperties vectorStoreProperties = SpringUtil.getBean(QdrantVectorStoreProperties.class);
  475 + // 参考 QdrantVectorStoreAutoConfiguration 实现,创建 QdrantClient 对象
  476 + QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder(
  477 + vectorStoreProperties.getHost(), vectorStoreProperties.getPort(), vectorStoreProperties.isUseTls());
  478 + if (StrUtil.isNotEmpty(vectorStoreProperties.getApiKey())) {
  479 + grpcClientBuilder.withApiKey(vectorStoreProperties.getApiKey());
  480 + }
  481 + QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build());
  482 + // 参考 QdrantVectorStoreAutoConfiguration 实现,实现 batchingStrategy
  483 + BatchingStrategy batchingStrategy = ReflectUtil.invoke(configuration, "batchingStrategy");
  484 +
  485 + // 创建 QdrantVectorStore 对象
  486 + ObjectProvider<ObservationRegistry> observationRegistry = new ObjectProvider<>() {
  487 +
  488 + @Override
  489 + public ObservationRegistry getObject() throws BeansException {
  490 + return SpringUtil.getBean(ObservationRegistry.class);
  491 + }
  492 +
  493 + };
  494 + ObjectProvider <VectorStoreObservationConvention> customObservationConvention = new ObjectProvider<>() {
  495 +
  496 + @Override
  497 + public VectorStoreObservationConvention getObject() throws BeansException {
  498 + return new DefaultVectorStoreObservationConvention();
  499 + }
  500 +
  501 + };
  502 + return configuration.vectorStore(embeddingModel, vectorStoreProperties, qdrantClient,
  503 + observationRegistry, customObservationConvention, batchingStrategy);
  504 + }
  505 +
459 506 }
... ...
yudao-server/src/main/resources/application-dev.yaml
... ... @@ -4,6 +4,11 @@ server:
4 4 --- #################### 数据库相关配置 ####################
5 5  
6 6 spring:
  7 + spring:
  8 + autoconfigure:
  9 + exclude:
  10 + - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
  11 + - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
7 12 # 数据源配置项
8 13 datasource:
9 14 druid: # Druid 【监控】相关的全局配置
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -3,13 +3,15 @@ server:
3 3  
4 4 --- #################### 数据库相关配置 ####################
5 5 spring:
6   - # 数据源配置项
7 6 autoconfigure:
8 7 exclude:
9 8 - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置
10 9 - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
11 10 - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
12 11 - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
  12 + - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
  13 + - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
  14 + # 数据源配置项
13 15 datasource:
14 16 druid: # Druid 【监控】相关的全局配置
15 17 web-stat-filter:
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -151,6 +151,12 @@ spring:
151 151 redis:
152 152 index: default-index
153 153 prefix: "default:"
  154 + qdrant:
  155 + collection-name: knowledge_segment
  156 + host: 127.0.0.1
  157 + port: 6334
  158 + use-tls: false
  159 + api-key:
154 160 qianfan: # 文心一言
155 161 api-key: x0cuLZ7XsaTCU08vuJWO87Lg
156 162 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
... ...