初始化

This commit is contained in:
2026-02-22 18:55:40 +08:00
commit 8392cdd861
496 changed files with 45020 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.easyagents</groupId>
<artifactId>easy-agents-store</artifactId>
<version>${revision}</version>
</parent>
<name>easy-agents-store-qdrant</name>
<artifactId>easy-agents-store-qdrant</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.easyagents</groupId>
<artifactId>easy-agents-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.65.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.65.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.65.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.qdrant</groupId>
<artifactId>client</artifactId>
<version>1.14.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,186 @@
/*
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.easyagents.store.qdrant;
import com.easyagents.core.document.Document;
import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.SearchWrapper;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import com.easyagents.core.util.CollectionUtil;
import com.easyagents.core.util.StringUtil;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections;
import io.qdrant.client.grpc.JsonWithInt;
import io.qdrant.client.grpc.Points;
import io.qdrant.client.grpc.Points.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.QueryFactory.nearest;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import static io.qdrant.client.WithPayloadSelectorFactory.enable;
public class QdrantVectorStore extends DocumentStore {
private final QdrantVectorStoreConfig config;
private final QdrantClient client;
private final String defaultCollectionName;
private boolean isCreateCollection = false;
public QdrantVectorStore(QdrantVectorStoreConfig config) throws IOException {
this.config = config;
this.defaultCollectionName = config.getDefaultCollectionName();
String uri = config.getUri();
int port = 6334;
QdrantGrpcClient.Builder builder;
if (StringUtil.hasText(config.getCaPath())) {
ManagedChannel channel = Grpc.newChannelBuilder(
uri,
TlsChannelCredentials.newBuilder().trustManager(new File(config.getCaPath())).build()
).build();
builder = QdrantGrpcClient.newBuilder(channel, true);
} else {
if (uri.contains(":")) {
uri = uri.split(":")[0];
port = Integer.parseInt(uri.split(":")[1]);
}
builder = QdrantGrpcClient.newBuilder(uri, port, false);
}
if (StringUtil.hasText(config.getApiKey())) {
builder.withApiKey(config.getApiKey());
}
this.client = new QdrantClient(builder.build());
}
@Override
public StoreResult doStore(List<Document> documents, StoreOptions options) {
List<PointStruct> points = new ArrayList<>();
int size = 1024;
for (Document doc : documents) {
size = doc.getVector().length;
Map<String, JsonWithInt.Value> payload = new HashMap<>();
payload.put("content", value(doc.getContent()));
points.add(PointStruct.newBuilder()
.setId(id(Long.parseLong(doc.getId().toString())))
.setVectors(vectors(doc.getVector()))
.putAllPayload(payload)
.build());
}
try {
String collectionName = options.getCollectionNameOrDefault(defaultCollectionName);
if (config.isAutoCreateCollection() && !isCreateCollection) {
Boolean exists = client.collectionExistsAsync(collectionName).get();
if (!exists) {
client.createCollectionAsync(collectionName, Collections.VectorParams.newBuilder()
.setDistance(Collections.Distance.Cosine)
.setSize(size)
.build())
.get();
}
} else {
isCreateCollection = true;
}
if (CollectionUtil.hasItems(points)) {
client.upsertAsync(collectionName, points).get();
}
return StoreResult.successWithIds(documents);
} catch (Exception e) {
return StoreResult.fail();
}
}
@Override
public StoreResult doDelete(Collection<?> ids, StoreOptions options) {
try {
String collectionName = options.getCollectionNameOrDefault(defaultCollectionName);
List<PointId> pointIds = ids.stream()
.map(id -> id((Long) id))
.collect(Collectors.toList());
client.deleteAsync(collectionName, pointIds).get();
return StoreResult.success();
} catch (Exception e) {
return StoreResult.fail();
}
}
@Override
public StoreResult doUpdate(List<Document> documents, StoreOptions options) {
try {
List<PointStruct> points = new ArrayList<>();
for (Document doc : documents) {
Map<String, JsonWithInt.Value> payload = new HashMap<>();
payload.put("content", value(doc.getContent()));
points.add(PointStruct.newBuilder()
.setId(id(Long.parseLong(doc.getId().toString())))
.setVectors(vectors(doc.getVector()))
.putAllPayload(payload)
.build());
}
String collectionName = options.getCollectionNameOrDefault(defaultCollectionName);
if (CollectionUtil.hasItems(points)) {
client.upsertAsync(collectionName, points).get();
}
return StoreResult.successWithIds(documents);
} catch (Exception e) {
return StoreResult.fail();
}
}
@Override
public List<Document> doSearch(SearchWrapper wrapper, StoreOptions options) {
List<Document> documents = new ArrayList<>();
try {
String collectionName = options.getCollectionNameOrDefault(defaultCollectionName);
QueryPoints.Builder query = QueryPoints.newBuilder()
.setCollectionName(collectionName)
.setLimit(wrapper.getMaxResults())
.setWithVectors(Points.WithVectorsSelector.newBuilder().setEnable(true).build())
.setWithPayload(enable(true));
if (wrapper.getVector() != null) {
query.setQuery(nearest(wrapper.getVector()));
}
if (StringUtil.hasText(wrapper.getText())) {
query.setFilter(Filter.newBuilder().addMust(matchKeyword("content", wrapper.getText())));
}
List<ScoredPoint> data = client.queryAsync(query.build()).get();
for (ScoredPoint point : data) {
Document doc = new Document();
doc.setId(point.getId().getNum());
doc.setVector(point.getVectors().getVector().getDataList());
doc.setContent(point.getPayloadMap().get("content").getStringValue());
documents.add(doc);
}
return documents;
} catch (Exception e) {
return documents;
}
}
public QdrantClient getClient() {
return client;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.easyagents.store.qdrant;
import com.easyagents.core.store.DocumentStoreConfig;
import com.easyagents.core.util.StringUtil;
public class QdrantVectorStoreConfig implements DocumentStoreConfig {
private String uri;
private String caPath;
private String defaultCollectionName;
private String apiKey;
private boolean autoCreateCollection = true;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getCaPath() {
return caPath;
}
public void setCaPath(String caPath) {
this.caPath = caPath;
}
public String getDefaultCollectionName() {
return defaultCollectionName;
}
public void setDefaultCollectionName(String defaultCollectionName) {
this.defaultCollectionName = defaultCollectionName;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public boolean isAutoCreateCollection() {
return autoCreateCollection;
}
public void setAutoCreateCollection(boolean autoCreateCollection) {
this.autoCreateCollection = autoCreateCollection;
}
@Override
public boolean checkAvailable() {
return StringUtil.hasText(this.uri);
}
}

View File

@@ -0,0 +1,65 @@
package com.easyagents.store.qdrant;
import com.easyagents.core.document.Document;
import com.easyagents.core.store.SearchWrapper;
import com.easyagents.core.store.StoreOptions;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class QdrantVectorStoreTest {
@Test
public void testSaveVectors() throws Exception {
QdrantVectorStore db = getDb();
StoreOptions options = new StoreOptions();
options.setCollectionName("test_collection1");
List<Document> list = new ArrayList<>();
Document doc1 = new Document();
doc1.setId(1L);
doc1.setContent("test1");
doc1.setVector(new float[]{5.2f, 4.4f});
list.add(doc1);
Document doc2 = new Document();
doc2.setId(2L);
doc2.setContent("test2");
doc2.setVector(new float[]{5.2f, 3.9f});
list.add(doc2);
Document doc3 = new Document();
doc3.setId(3);
doc3.setContent("test3");
doc3.setVector(new float[]{4.9f, 3.4f});
list.add(doc3);
db.store(list, options);
}
@Test
public void testQuery() throws Exception {
QdrantVectorStore db = getDb();;
StoreOptions options = new StoreOptions();
options.setCollectionName("test_collection1");
SearchWrapper search = new SearchWrapper();
search.setVector(new float[]{5.2f, 3.9f});
//search.setText("test1");
search.setMaxResults(1);
List<Document> record = db.search(search, options);
System.out.println(record);
}
@Test
public void testDelete() throws Exception {
QdrantVectorStore db = getDb();
StoreOptions options = new StoreOptions();
options.setCollectionName("test_collection1");
db.delete(Collections.singletonList(3L), options);
}
private QdrantVectorStore getDb() throws Exception {
QdrantVectorStoreConfig config = new QdrantVectorStoreConfig();
config.setUri("localhost");
config.setDefaultCollectionName("test_collection1");
return new QdrantVectorStore(config);
}
}