初始化
This commit is contained in:
61
easy-agents-store/easy-agents-store-qdrant/pom.xml
Normal file
61
easy-agents-store/easy-agents-store-qdrant/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user