{
+
+ private Integer code;
+ private String msg;
+ private T data;
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalParam.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalParam.java
new file mode 100644
index 00000000000..b35db1d0c0b
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalParam.java
@@ -0,0 +1,116 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * As a constraint of all engine type only
+ *
+ * @see RetrievalType
+ */
+public abstract class RetrievalParam {
+
+ @Getter
+ @Setter
+ @Builder
+ public static class IVFPQParam extends RetrievalParam {
+
+ @Builder.Default
+ private MetricType metricType = MetricType.INNER_PRODUCT;
+ /**
+ * number of buckets for indexing
+ *
+ * default 2048
+ */
+ private Integer ncentroids;
+ /**
+ * the number of sub vector
+ *
+ * default 64, must be a multiple of 4
+ */
+ private Integer nsubvector;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class HNSWParam extends RetrievalParam {
+
+ @Builder.Default
+ private MetricType metricType = MetricType.INNER_PRODUCT;
+ /**
+ * neighbors number of each node
+ *
+ * default 32
+ */
+ private Integer nlinks;
+ /**
+ * expansion factor at construction time
+ *
+ * default 40
+ * The higher the value, the better the construction effect, and the longer it takes
+ */
+ @SerializedName("efConstruction")
+ private Integer efConstruction;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class GPUParam extends RetrievalParam {
+
+ @Builder.Default
+ private MetricType metricType = MetricType.INNER_PRODUCT;
+ /**
+ * number of buckets for indexing
+ *
+ * default 2048
+ */
+ private Integer ncentroids;
+ /**
+ * the number of sub vector
+ *
+ * default 64
+ */
+ private Integer nsubvector;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class IVFFLATParam extends RetrievalParam {
+
+ @Builder.Default
+ private MetricType metricType = MetricType.INNER_PRODUCT;
+ /**
+ * number of buckets for indexing
+ *
+ * default 2048
+ */
+ private Integer ncentroids;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class BINARYIVFParam extends RetrievalParam {
+
+ /**
+ * coarse cluster center number
+ *
+ * default 256
+ */
+ private Integer ncentroids;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class FLAT extends RetrievalParam {
+
+ @Builder.Default
+ private MetricType metricType = MetricType.INNER_PRODUCT;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalType.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalType.java
new file mode 100644
index 00000000000..63538bb16a4
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/RetrievalType.java
@@ -0,0 +1,20 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Getter;
+
+public enum RetrievalType {
+
+ IVFPQ(RetrievalParam.IVFPQParam.class),
+ HNSW(RetrievalParam.HNSWParam.class),
+ GPU(RetrievalParam.GPUParam.class),
+ IVFFLAT(RetrievalParam.IVFFLATParam.class),
+ BINARYIVF(RetrievalParam.BINARYIVFParam.class),
+ FLAT(RetrievalParam.FLAT.class);
+
+ @Getter
+ private Class extends RetrievalParam> paramClass;
+
+ RetrievalType(Class extends RetrievalParam> paramClass) {
+ this.paramClass = paramClass;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchRequest.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchRequest.java
new file mode 100644
index 00000000000..fa8abc93638
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchRequest.java
@@ -0,0 +1,35 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Builder
+class SearchRequest {
+
+ private QueryParam query;
+ private Integer size;
+ private List fields;
+
+ @Getter
+ @Setter
+ @Builder
+ public static class QueryParam {
+
+ private List sum;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class VectorParam {
+
+ private String field;
+ private List feature;
+ private Double minScore;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchResponse.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchResponse.java
new file mode 100644
index 00000000000..00dbf7006a6
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SearchResponse.java
@@ -0,0 +1,48 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.Map;
+
+@Getter
+@Setter
+@Builder
+class SearchResponse {
+
+ private Integer took;
+ @SerializedName("timed_out")
+ private Boolean timeout;
+ /**
+ * not support shards yet
+ */
+ @SerializedName("_shards")
+ private Object shards;
+ private Hit hits;
+
+ @Getter
+ @Setter
+ @Builder
+ public static class Hit {
+
+ private Integer total;
+ private Double maxScore;
+ private List hits;
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ public static class SearchedDocument {
+
+ @SerializedName("_id")
+ private String id;
+ @SerializedName("_score")
+ private Double score;
+ @SerializedName("_source")
+ private Map source;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceEngine.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceEngine.java
new file mode 100644
index 00000000000..1ede675683d
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceEngine.java
@@ -0,0 +1,72 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class SpaceEngine {
+
+ private String name;
+ private Long indexSize;
+ private RetrievalType retrievalType;
+ private RetrievalParam retrievalParam;
+
+ public SpaceEngine() {
+
+ }
+
+ public SpaceEngine(String name, Long indexSize, RetrievalType retrievalType, RetrievalParam retrievalParam) {
+ setName(name);
+ setIndexSize(indexSize);
+ setRetrievalType(retrievalType);
+ setRetrievalParam(retrievalParam);
+ }
+
+ public void setRetrievalParam(RetrievalParam retrievalParam) {
+ // do some constraint check
+ Class extends RetrievalParam> clazz = retrievalType.getParamClass();
+ if (!clazz.isInstance(retrievalParam)) {
+ throw new UnsupportedOperationException(
+ String.format("can't assign unknown param of engine %s, please use class %s to assign engine param",
+ retrievalType.name(), clazz.getSimpleName()));
+ }
+ this.retrievalParam = retrievalParam;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private String name;
+ private Long indexSize;
+ private RetrievalType retrievalType;
+ private RetrievalParam retrievalParam;
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder indexSize(Long indexSize) {
+ this.indexSize = indexSize;
+ return this;
+ }
+
+ public Builder retrievalType(RetrievalType retrievalType) {
+ this.retrievalType = retrievalType;
+ return this;
+ }
+
+ public Builder retrievalParam(RetrievalParam retrievalParam) {
+ this.retrievalParam = retrievalParam;
+ return this;
+ }
+
+ public SpaceEngine build() {
+ return new SpaceEngine(name, indexSize, retrievalType, retrievalParam);
+ }
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyParam.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyParam.java
new file mode 100644
index 00000000000..ad901981d22
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyParam.java
@@ -0,0 +1,124 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * As a constraint type of all Space property only
+ *
+ * @see CreateSpaceRequest
+ */
+public abstract class SpacePropertyParam {
+
+ protected SpacePropertyType type;
+
+ SpacePropertyParam(SpacePropertyType type) {
+ this.type = type;
+ }
+
+ @Getter
+ @Setter
+ public static class StringParam extends SpacePropertyParam {
+
+ /**
+ * whether to create an index
+ */
+ private Boolean index;
+ /**
+ * whether to allow multipart value
+ */
+ private Boolean array;
+
+ public StringParam() {
+ super(SpacePropertyType.STRING);
+ }
+
+ @Builder
+ public StringParam(Boolean index, Boolean array) {
+ this();
+ this.index = index;
+ this.array = array;
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class IntegerParam extends SpacePropertyParam {
+
+ /**
+ * whether to create an index
+ *
+ * set to true to support the use of numeric range filtering queries (not supported in langchain4j now)
+ */
+ private Boolean index;
+
+ public IntegerParam() {
+ super(SpacePropertyType.INTEGER);
+ }
+
+ @Builder
+ public IntegerParam(Boolean index) {
+ this();
+ this.index = index;
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class FloatParam extends SpacePropertyParam {
+
+ /**
+ * whether to create an index
+ *
+ * set to true to support the use of numeric range filtering queries (not supported in langchain4j now)
+ */
+ private Boolean index;
+
+ public FloatParam() {
+ super(SpacePropertyType.FLOAT);
+ }
+
+ @Builder
+ public FloatParam(Boolean index) {
+ this();
+ this.index = index;
+ }
+ }
+
+ @Getter
+ @Setter
+ public static class VectorParam extends SpacePropertyParam {
+
+ private Boolean index;
+ private Integer dimension;
+ /**
+ * "RocksDB" or "MemoryOnly". For HNSW and IVFFLAT and FLAT, it can only be run in MemoryOnly mode.
+ *
+ * @see SpaceStoreType
+ */
+ private SpaceStoreType storeType;
+ private SpaceStoreParam storeParam;
+ private String modelId;
+ /**
+ * default not normalized. if you set "normalization", "normal" it will normalized
+ */
+ private String format;
+
+ public VectorParam() {
+ super(SpacePropertyType.VECTOR);
+ }
+
+ @Builder
+ public VectorParam(Boolean index, Integer dimension, SpaceStoreType storeType,
+ SpaceStoreParam storeParam, String modelId, String format) {
+ this();
+ this.index = index;
+ this.dimension = dimension;
+ this.storeType = storeType;
+ this.storeParam = storeParam;
+ this.modelId = modelId;
+ this.format = format;
+ }
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyType.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyType.java
new file mode 100644
index 00000000000..de5f764708c
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpacePropertyType.java
@@ -0,0 +1,26 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+
+public enum SpacePropertyType {
+
+ /**
+ * keyword is equivalent to string
+ */
+ @SerializedName("string")
+ STRING(SpacePropertyParam.StringParam.class),
+ @SerializedName("integer")
+ INTEGER(SpacePropertyParam.IntegerParam.class),
+ @SerializedName("float")
+ FLOAT(SpacePropertyParam.FloatParam.class),
+ @SerializedName("vector")
+ VECTOR(SpacePropertyParam.VectorParam.class);
+
+ @Getter
+ private final Class extends SpacePropertyParam> paramClass;
+
+ SpacePropertyType(Class extends SpacePropertyParam> paramClass) {
+ this.paramClass = paramClass;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreParam.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreParam.java
new file mode 100644
index 00000000000..48b4075bd83
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreParam.java
@@ -0,0 +1,25 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class SpaceStoreParam {
+
+ /**
+ * It means you will use so much memory, the excess will be kept to disk. For MemoryOnly, this parameter is invalid.
+ */
+ private Integer cacheSize;
+ private CompressRate compress;
+
+ @Getter
+ @Setter
+ @Builder
+ public static class CompressRate {
+
+ private Integer rate;
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreType.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreType.java
new file mode 100644
index 00000000000..0b09e6927e1
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/SpaceStoreType.java
@@ -0,0 +1,14 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+
+public enum SpaceStoreType {
+
+ @SerializedName("MemoryOnly")
+ MEMORY_ONLY,
+ @SerializedName("Mmap")
+ M_MAP,
+ @SerializedName("RocksDB")
+ ROCKS_DB
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchApi.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchApi.java
new file mode 100644
index 00000000000..ea522fc963b
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchApi.java
@@ -0,0 +1,44 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import okhttp3.RequestBody;
+import retrofit2.Call;
+import retrofit2.http.*;
+
+import java.util.List;
+
+public interface VearchApi {
+
+ int OK = 200;
+
+ /* Database Operation */
+
+ @GET("/list/db")
+ Call>> listDatabase();
+
+ @PUT("/db/_create")
+ Call> createDatabase(@Body CreateDatabaseRequest request);
+
+ @GET("/list/space")
+ Call>> listSpaceOfDatabase(@Query("db") String dbName);
+
+ /* Space (like a table in relational database) Operation */
+
+ @PUT("/space/{db}/_create")
+ Call> createSpace(@Path("db") String dbName,
+ @Body CreateSpaceRequest request);
+
+ /* Document Operation */
+
+ @POST("/{db}/{space}/_bulk")
+ Call> bulk(@Path("db") String db,
+ @Path("space") String space,
+ @Body RequestBody requestBody);
+
+ @POST("/{db}/{space}/_search")
+ Call search(@Path("db") String db,
+ @Path("space") String space,
+ @Body SearchRequest request);
+
+ @DELETE("/space/{db}/{space}")
+ Call deleteSpace(@Path("db") String dbName, @Path("space") String spaceName);
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchClient.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchClient.java
new file mode 100644
index 00000000000..57a114cdd61
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchClient.java
@@ -0,0 +1,202 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import lombok.Builder;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
+import static dev.langchain4j.store.embedding.vearch.VearchApi.OK;
+
+class VearchClient {
+
+ private static final Gson GSON = new GsonBuilder()
+ .setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+
+ private final VearchApi vearchApi;
+
+ @Builder
+ public VearchClient(String baseUrl, Duration timeout) {
+ OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .callTimeout(timeout)
+ .connectTimeout(timeout)
+ .readTimeout(timeout)
+ .writeTimeout(timeout)
+ .build();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create(GSON))
+ .build();
+
+ vearchApi = retrofit.create(VearchApi.class);
+ }
+
+ public List listDatabase() {
+ try {
+ Response>> response = vearchApi.listDatabase().execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ ResponseWrapper> wrapper = response.body();
+ if (wrapper.getCode() != OK) {
+ throw toException(wrapper);
+ }
+ return wrapper.getData();
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public CreateDatabaseResponse createDatabase(CreateDatabaseRequest request) {
+ try {
+ Response> response = vearchApi.createDatabase(request).execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ ResponseWrapper wrapper = response.body();
+ if (wrapper.getCode() != OK) {
+ throw toException(wrapper);
+ }
+ return wrapper.getData();
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List listSpace(String dbName) {
+ try {
+ Response>> response = vearchApi.listSpaceOfDatabase(dbName).execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ ResponseWrapper> wrapper = response.body();
+ if (wrapper.getCode() != OK) {
+ throw toException(wrapper);
+ }
+ return wrapper.getData();
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public CreateSpaceResponse createSpace(String dbName, CreateSpaceRequest request) {
+ try {
+ Response> response = vearchApi.createSpace(dbName, request).execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ ResponseWrapper wrapper = response.body();
+ if (wrapper.getCode() != OK) {
+ throw toException(wrapper);
+ }
+ return wrapper.getData();
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void bulk(String dbName, String spaceName, BulkRequest request) {
+ try {
+ StringBuilder bodyString = new StringBuilder();
+ for (Map document : request.getDocuments()) {
+ Map fieldsExceptId = new HashMap<>();
+ for (Map.Entry entry : document.entrySet()) {
+ String fieldName = entry.getKey();
+ Object value = entry.getValue();
+
+ if ("_id".equals(fieldName)) {
+ bodyString.append("{\"index\": {\"_id\": \"").append(value).append("\"}}\n");
+ } else {
+ fieldsExceptId.put(fieldName, value);
+ }
+ }
+ bodyString.append(GSON.toJson(fieldsExceptId)).append("\n");
+ }
+ RequestBody body = RequestBody.create(bodyString.toString(), MediaType.parse("application/json; charset=utf-8"));
+ Response> response = vearchApi.bulk(dbName, spaceName, body).execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ List bulkResponses = response.body();
+ bulkResponses.forEach(bulkResponse -> {
+ if (bulkResponse.getStatus() != OK) {
+ throw toException(bulkResponse.getStatus(), bulkResponse.getError());
+ }
+ });
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public SearchResponse search(String dbName, String spaceName, SearchRequest request) {
+ try {
+ Response response = vearchApi.search(dbName, spaceName, request).execute();
+
+ if (response.isSuccessful() && response.body() != null) {
+ SearchResponse searchResponse = response.body();
+ if (Boolean.TRUE.equals(searchResponse.getTimeout())) {
+ throw new RuntimeException("Search Timeout");
+ }
+ return searchResponse;
+ } else {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void deleteSpace(String databaseName, String spaceName) {
+ try {
+ Response response = vearchApi.deleteSpace(databaseName, spaceName).execute();
+
+ if (!response.isSuccessful()) {
+ throw toException(response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private RuntimeException toException(Response> response) throws IOException {
+ int code = response.code();
+ String body = response.errorBody().string();
+
+ String errorMessage = String.format("status code: %s; body: %s", code, body);
+ return new RuntimeException(errorMessage);
+ }
+
+ private RuntimeException toException(ResponseWrapper> responseWrapper) {
+ return toException(responseWrapper.getCode(), responseWrapper.getMsg());
+ }
+
+ private RuntimeException toException(int code, String msg) {
+ String errorMessage = String.format("code: %s; message: %s", code, msg);
+
+ return new RuntimeException(errorMessage);
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchConfig.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchConfig.java
new file mode 100644
index 00000000000..609b717bf71
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchConfig.java
@@ -0,0 +1,64 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.singletonList;
+
+@Getter
+@Setter
+@Builder
+public class VearchConfig {
+
+ private String databaseName;
+ private String spaceName;
+ private SpaceEngine spaceEngine;
+ /**
+ * This attribute's key set should contain
+ * {@link VearchConfig#embeddingFieldName}, {@link VearchConfig#textFieldName} and {@link VearchConfig#metadataFieldNames}
+ */
+ private Map properties;
+ @Builder.Default
+ private String embeddingFieldName = "embedding";
+ @Builder.Default
+ private String textFieldName = "text";
+ private List modelParams;
+ /**
+ * This attribute should be the subset of {@link VearchConfig#properties}'s key set
+ */
+ private List metadataFieldNames;
+
+ public static VearchConfig getDefaultConfig() {
+ // init properties
+ Map properties = new HashMap<>(4);
+ properties.put("embedding", SpacePropertyParam.VectorParam.builder()
+ .index(true)
+ .storeType(SpaceStoreType.MEMORY_ONLY)
+ .dimension(384)
+ .build());
+ properties.put("text", SpacePropertyParam.StringParam.builder().build());
+
+ return VearchConfig.builder()
+ .spaceEngine(SpaceEngine.builder()
+ .name("gamma")
+ .indexSize(1L)
+ .retrievalType(RetrievalType.FLAT)
+ .retrievalParam(RetrievalParam.FLAT.builder()
+ .build())
+ .build())
+ .properties(properties)
+ .databaseName("embedding_db")
+ .spaceName("embedding_space")
+ .modelParams(singletonList(ModelParam.builder()
+ .modelId("vgg16")
+ .fields(singletonList("string"))
+ .out("feature")
+ .build()))
+ .build();
+ }
+}
diff --git a/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchEmbeddingStore.java b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchEmbeddingStore.java
new file mode 100644
index 00000000000..8c3bd8c16a0
--- /dev/null
+++ b/langchain4j-vearch/src/main/java/dev/langchain4j/store/embedding/vearch/VearchEmbeddingStore.java
@@ -0,0 +1,270 @@
+package dev.langchain4j.store.embedding.vearch;
+
+import dev.langchain4j.data.document.Metadata;
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.store.embedding.CosineSimilarity;
+import dev.langchain4j.store.embedding.EmbeddingMatch;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.RelevanceScore;
+
+import java.time.Duration;
+import java.util.*;
+
+import static dev.langchain4j.internal.Utils.*;
+import static dev.langchain4j.internal.ValidationUtils.*;
+import static java.time.Duration.ofSeconds;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+
+public class VearchEmbeddingStore implements EmbeddingStore {
+
+ private final VearchConfig vearchConfig;
+ private final VearchClient vearchClient;
+ /**
+ * whether to normalize embedding when add to embedding store
+ */
+ private final boolean normalizeEmbeddings;
+
+ public VearchEmbeddingStore(String baseUrl,
+ Duration timeout,
+ VearchConfig vearchConfig,
+ Boolean normalizeEmbeddings) {
+ // Step 0: initialize some attribute
+ baseUrl = ensureNotNull(baseUrl, "baseUrl");
+ this.vearchConfig = getOrDefault(vearchConfig, VearchConfig.getDefaultConfig());
+ this.normalizeEmbeddings = getOrDefault(normalizeEmbeddings, false);
+
+ vearchClient = VearchClient.builder()
+ .baseUrl(baseUrl)
+ .timeout(getOrDefault(timeout, ofSeconds(60)))
+ .build();
+
+ // Step 1: check whether db exist, if not, create it
+ if (!isDatabaseExist(this.vearchConfig.getDatabaseName())) {
+ createDatabase(this.vearchConfig.getDatabaseName());
+ }
+
+ // Step 2: check whether space exist, if not, create it
+ if (!isSpaceExist(this.vearchConfig.getDatabaseName(), this.vearchConfig.getSpaceName())) {
+ createSpace(this.vearchConfig.getDatabaseName(), this.vearchConfig.getSpaceName());
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private VearchConfig vearchConfig;
+ private String baseUrl;
+ private Duration timeout;
+ private Boolean normalizeEmbeddings;
+
+ public Builder vearchConfig(VearchConfig vearchConfig) {
+ this.vearchConfig = vearchConfig;
+ return this;
+ }
+
+ public Builder baseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ return this;
+ }
+
+ public Builder timeout(Duration timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ /**
+ * Set whether to normalize embedding when add to embedding store
+ *
+ * @param normalizeEmbeddings whether to normalize embedding when add to embedding store
+ * @return builder
+ */
+ public Builder normalizeEmbeddings(Boolean normalizeEmbeddings) {
+ this.normalizeEmbeddings = normalizeEmbeddings;
+ return this;
+ }
+
+ public VearchEmbeddingStore build() {
+ return new VearchEmbeddingStore(baseUrl, timeout, vearchConfig, normalizeEmbeddings);
+ }
+ }
+
+ @Override
+ public String add(Embedding embedding) {
+ String id = randomUUID();
+ add(id, embedding);
+ return id;
+ }
+
+ @Override
+ public void add(String id, Embedding embedding) {
+ addInternal(id, embedding, null);
+ }
+
+ @Override
+ public String add(Embedding embedding, TextSegment textSegment) {
+ String id = randomUUID();
+ addInternal(id, embedding, textSegment);
+ return id;
+ }
+
+ @Override
+ public List addAll(List embeddings) {
+ List ids = embeddings.stream()
+ .map(ignored -> randomUUID())
+ .collect(toList());
+ addAllInternal(ids, embeddings, null);
+ return ids;
+ }
+
+ @Override
+ public List addAll(List embeddings, List embedded) {
+ List ids = embeddings.stream()
+ .map(ignored -> randomUUID())
+ .collect(toList());
+ addAllInternal(ids, embeddings, embedded);
+ return ids;
+ }
+
+ @Override
+ public List> findRelevant(Embedding referenceEmbedding, int maxResults, double minScore) {
+ double minSimilarity = CosineSimilarity.fromRelevanceScore(minScore);
+ List fields = new ArrayList<>(Arrays.asList(vearchConfig.getTextFieldName(), vearchConfig.getEmbeddingFieldName()));
+ fields.addAll(vearchConfig.getMetadataFieldNames());
+ SearchRequest request = SearchRequest.builder()
+ .query(SearchRequest.QueryParam.builder()
+ .sum(singletonList(SearchRequest.VectorParam.builder()
+ .field(vearchConfig.getEmbeddingFieldName())
+ .feature(referenceEmbedding.vectorAsList())
+ .minScore(minSimilarity)
+ .build()))
+ .build())
+ .size(maxResults)
+ .fields(fields)
+ .build();
+
+ SearchResponse response = vearchClient.search(vearchConfig.getDatabaseName(), vearchConfig.getSpaceName(), request);
+ return toEmbeddingMatch(response.getHits());
+ }
+
+ public void deleteSpace() {
+ vearchClient.deleteSpace(vearchConfig.getDatabaseName(), vearchConfig.getSpaceName());
+ }
+
+ private void addInternal(String id, Embedding embedding, TextSegment embedded) {
+ addAllInternal(singletonList(id), singletonList(embedding), embedded == null ? null : singletonList(embedded));
+ }
+
+ private void addAllInternal(List ids, List embeddings, List embedded) {
+ ids = ensureNotEmpty(ids, "ids");
+ embeddings = ensureNotEmpty(embeddings, "embeddings");
+ ensureTrue(ids.size() == embeddings.size(), "ids size is not equal to embeddings size");
+ ensureTrue(embedded == null || embeddings.size() == embedded.size(), "embeddings size is not equal to embedded size");
+
+ List