diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 5f7179e2..269da7ae 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -275,7 +275,7 @@ warm-flow:
# 向量库配置
vector-store:
- # 向量存储类型 可选(weaviate/milvus)
+ # 向量存储类型 可选(weaviate/milvus/qdrant)
# 如需修改向量库类型,请修改此配置值!
type: milvus
# Weaviate配置
@@ -287,3 +287,10 @@ vector-store:
milvus:
url: http://localhost:19530
collectionname: LocalKnowledge
+ # Qdrant配置
+ qdrant:
+ host: localhost
+ port: 6334
+ collectionname: LocalKnowledge
+ api-key:
+ use-tls: false
diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml
index ba2f10c0..6cb40321 100644
--- a/ruoyi-modules/ruoyi-chat/pom.xml
+++ b/ruoyi-modules/ruoyi-chat/pom.xml
@@ -91,6 +91,12 @@
${langchain4j.community.version}
+
+ dev.langchain4j
+ langchain4j-qdrant
+ ${langchain4j.community.version}
+
+
dev.langchain4j
langchain4j-mcp
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/VectorStoreProperties.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/VectorStoreProperties.java
index de93372a..83ff47d0 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/VectorStoreProperties.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/VectorStoreProperties.java
@@ -59,4 +59,37 @@ public class VectorStoreProperties {
*/
private String collectionname;
}
+
+ /**
+ * Qdrant配置
+ */
+ private Qdrant qdrant = new Qdrant();
+
+ @Data
+ public static class Qdrant {
+ /**
+ * 主机地址
+ */
+ private String host = "localhost";
+
+ /**
+ * gRPC端口
+ */
+ private int port = 6334;
+
+ /**
+ * 集合名称
+ */
+ private String collectionname = "LocalKnowledge";
+
+ /**
+ * API密钥(可选)
+ */
+ private String apiKey;
+
+ /**
+ * 是否启用TLS
+ */
+ private boolean useTls = false;
+ }
}
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java
index cc750a6b..a66ea66c 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java
@@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.ruoyi.config.VectorStoreProperties;
import org.ruoyi.service.vector.VectorStoreService;
import org.ruoyi.service.vector.impl.MilvusVectorStoreStrategy;
+import org.ruoyi.service.vector.impl.QdrantVectorStoreStrategy;
import org.ruoyi.service.vector.impl.WeaviateVectorStoreStrategy;
import org.springframework.stereotype.Component;
@@ -27,6 +28,7 @@ public class VectorStoreStrategyFactory {
private final VectorStoreProperties vectorStoreProperties;
private final WeaviateVectorStoreStrategy weaviateStrategy;
private final MilvusVectorStoreStrategy milvusStrategy;
+ private final QdrantVectorStoreStrategy qdrantStrategy;
private Map strategies;
@@ -35,6 +37,7 @@ public class VectorStoreStrategyFactory {
strategies = new HashMap<>();
strategies.put("weaviate", weaviateStrategy);
strategies.put("milvus", milvusStrategy);
+ strategies.put("qdrant", qdrantStrategy);
log.info("向量库策略工厂初始化完成,支持的策略: {}", strategies.keySet());
}
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/QdrantVectorStoreStrategy.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/QdrantVectorStoreStrategy.java
new file mode 100644
index 00000000..3bee80f4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/QdrantVectorStoreStrategy.java
@@ -0,0 +1,204 @@
+package org.ruoyi.service.vector.impl;
+
+import dev.langchain4j.data.document.Metadata;
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.filter.Filter;
+import dev.langchain4j.store.embedding.filter.MetadataFilterBuilder;
+import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
+import io.qdrant.client.QdrantClient;
+import io.qdrant.client.QdrantGrpcClient;
+import io.qdrant.client.grpc.Collections.Distance;
+import io.qdrant.client.grpc.Collections.VectorParams;
+import io.qdrant.client.grpc.JsonWithInt;
+import io.qdrant.client.grpc.Points.DenseVector;
+import io.qdrant.client.grpc.Points.Query;
+import io.qdrant.client.grpc.Points.QueryPoints;
+import io.qdrant.client.grpc.Points.ScoredPoint;
+import io.qdrant.client.grpc.Points.VectorInput;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.chat.service.chat.IChatModelService;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.config.VectorStoreProperties;
+import org.ruoyi.domain.bo.vector.QueryVectorBo;
+import org.ruoyi.domain.bo.vector.StoreEmbeddingBo;
+import org.ruoyi.factory.EmbeddingModelFactory;
+import org.springframework.stereotype.Component;
+
+import static io.qdrant.client.VectorInputFactory.vectorInput;
+import static io.qdrant.client.WithPayloadSelectorFactory.enable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+/**
+ * Qdrant向量库策略实现
+ */
+@Slf4j
+@Component
+public class QdrantVectorStoreStrategy extends AbstractVectorStoreStrategy {
+
+ private static final String VECTOR_STORE_TYPE = "qdrant";
+ private static final String TEXT_SEGMENT_KEY = "text_segment";
+ private static final String METADATA_FID_KEY = "fid";
+ private static final String METADATA_KID_KEY = "kid";
+ private static final String METADATA_DOC_ID_KEY = "docId";
+
+ public QdrantVectorStoreStrategy(VectorStoreProperties vectorStoreProperties,
+ IChatModelService chatModelService,
+ EmbeddingModelFactory embeddingModelFactory) {
+ super(vectorStoreProperties, embeddingModelFactory, chatModelService);
+ }
+
+ private EmbeddingStore getQdrantStore(String collectionName) {
+ VectorStoreProperties.Qdrant cfg = vectorStoreProperties.getQdrant();
+ QdrantEmbeddingStore.Builder builder = QdrantEmbeddingStore.builder()
+ .host(cfg.getHost())
+ .port(cfg.getPort())
+ .collectionName(collectionName)
+ .useTls(cfg.isUseTls());
+ if (cfg.getApiKey() != null && !cfg.getApiKey().isEmpty()) {
+ builder.apiKey(cfg.getApiKey());
+ }
+ return builder.build();
+ }
+
+ private QdrantClient buildQdrantClient() {
+ VectorStoreProperties.Qdrant cfg = vectorStoreProperties.getQdrant();
+ QdrantGrpcClient.Builder grpcBuilder = QdrantGrpcClient.newBuilder(cfg.getHost(), cfg.getPort(), cfg.isUseTls());
+ if (cfg.getApiKey() != null && !cfg.getApiKey().isEmpty()) {
+ grpcBuilder.withApiKey(cfg.getApiKey());
+ }
+ return new QdrantClient(grpcBuilder.build());
+ }
+
+ private int getModelDimension(String modelName) {
+ return chatModelService.selectModelByName(modelName).getModelDimension();
+ }
+
+ @Override
+ public String getVectorStoreType() {
+ return VECTOR_STORE_TYPE;
+ }
+
+ @Override
+ public void createSchema(String kid, String modelName) {
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + kid;
+ int dimension = getModelDimension(modelName);
+ try (QdrantClient client = buildQdrantClient()) {
+ Boolean exists = client.collectionExistsAsync(collectionName).get();
+ if (!exists) {
+ VectorParams params = VectorParams.newBuilder()
+ .setSize(dimension)
+ .setDistance(Distance.Cosine)
+ .build();
+ client.createCollectionAsync(collectionName, params).get();
+ log.info("Qdrant集合创建成功: {}, dimension: {}", collectionName, dimension);
+ } else {
+ log.info("Qdrant集合已存在: {}", collectionName);
+ }
+ } catch (Exception e) {
+ log.error("Qdrant集合创建失败: {}", collectionName, e);
+ throw new ServiceException("Qdrant集合创建失败: " + collectionName);
+ }
+ }
+
+ @Override
+ public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
+ EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName());
+ List chunkList = storeEmbeddingBo.getChunkList();
+ List fidList = storeEmbeddingBo.getFids();
+ String kid = storeEmbeddingBo.getKid();
+ String docId = storeEmbeddingBo.getDocId();
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + kid;
+
+ EmbeddingStore embeddingStore = getQdrantStore(collectionName);
+
+ log.info("Qdrant向量存储条数记录: {}", chunkList.size());
+ long startTime = System.currentTimeMillis();
+
+ IntStream.range(0, chunkList.size()).forEach(i -> {
+ String text = chunkList.get(i);
+ String fid = fidList.get(i);
+ Metadata metadata = new Metadata();
+ metadata.put(METADATA_FID_KEY, fid);
+ metadata.put(METADATA_KID_KEY, kid);
+ metadata.put(METADATA_DOC_ID_KEY, docId);
+ TextSegment textSegment = TextSegment.from(text, metadata);
+ Embedding embedding = embeddingModel.embed(text).content();
+ embeddingStore.add(embedding, textSegment);
+ });
+
+ long endTime = System.currentTimeMillis();
+ log.info("Qdrant向量存储完成消耗时间:{}秒", (endTime - startTime) / 1000);
+ }
+
+ @Override
+ public List getQueryVector(QueryVectorBo queryVectorBo) {
+ EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName());
+ Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content();
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + queryVectorBo.getKid();
+
+ List vector = new ArrayList<>();
+ for (float f : queryEmbedding.vector()) {
+ vector.add(f);
+ }
+
+ try (QdrantClient client = buildQdrantClient()) {
+ QueryPoints request = QueryPoints.newBuilder()
+ .setCollectionName(collectionName)
+ .setQuery(Query.newBuilder()
+ .setNearest(vectorInput(vector))
+ .build())
+ .setLimit(queryVectorBo.getMaxResults())
+ .setWithPayload(enable(true))
+ .build();
+
+ List results = client.queryAsync(request).get();
+ List resultList = new ArrayList<>();
+ for (ScoredPoint point : results) {
+ JsonWithInt.Value textValue = point.getPayloadMap().get(TEXT_SEGMENT_KEY);
+ if (textValue != null && textValue.hasStringValue()) {
+ resultList.add(textValue.getStringValue());
+ }
+ }
+ return resultList;
+ } catch (Exception e) {
+ log.error("Qdrant查询失败: {}", collectionName, e);
+ throw new ServiceException("Qdrant向量查询失败");
+ }
+ }
+
+ @Override
+ public void removeById(String id, String modelName) {
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + id;
+ try (QdrantClient client = buildQdrantClient()) {
+ client.deleteCollectionAsync(collectionName).get();
+ log.info("Qdrant成功删除集合: {}", collectionName);
+ } catch (Exception e) {
+ log.error("Qdrant删除集合失败: {}", collectionName, e);
+ throw new ServiceException("失败删除向量数据!");
+ }
+ }
+
+ @Override
+ public void removeByDocId(String docId, String kid) {
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + kid;
+ EmbeddingStore embeddingStore = getQdrantStore(collectionName);
+ Filter filter = MetadataFilterBuilder.metadataKey(METADATA_DOC_ID_KEY).isEqualTo(docId);
+ embeddingStore.removeAll(filter);
+ log.info("Qdrant成功删除 docId={} 的所有向量数据", docId);
+ }
+
+ @Override
+ public void removeByFid(String fid, String kid) {
+ String collectionName = vectorStoreProperties.getQdrant().getCollectionname() + kid;
+ EmbeddingStore embeddingStore = getQdrantStore(collectionName);
+ Filter filter = MetadataFilterBuilder.metadataKey(METADATA_FID_KEY).isEqualTo(fid);
+ embeddingStore.removeAll(filter);
+ log.info("Qdrant成功删除 fid={} 的所有向量数据", fid);
+ }
+}