class Builder {

        private String serverUrl;
        private String apiKey;
        private String userName;
        private String password;
        private String serviceName;
        private String region;
        private AwsSdk2TransportOptions options;
        private String indexName = "default";
        private OpenSearchClient openSearchClient;

        public Builder serverUrl(String serverUrl) {
            this.serverUrl = serverUrl;
            return this;
        }

        public Builder apiKey(String apiKey) {
            this.apiKey = apiKey;
            return this;
        }

        public Builder userName(String userName) {
            this.userName = userName;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        public Builder serviceName(String serviceName) {
            this.serviceName = serviceName;
            return this;
        }

        public Builder region(String region) {
            this.region = region;
            return this;
        }

        public Builder options(AwsSdk2TransportOptions options) {
            this.options = options;
            return this;
        }

        public Builder indexName(String indexName) {
            this.indexName = indexName;
            return this;
        }

        public Builder openSearchClient(OpenSearchClient openSearchClient) {
            this.openSearchClient = openSearchClient;
            return this;
        }
        public OpenSearchEmbeddingStore build() {
            if (openSearchClient != null) {
                return new OpenSearchEmbeddingStore(openSearchClient, indexName);
            }
            if (!isNullOrBlank(serviceName) && !isNullOrBlank(region) && options != null) {
                return new OpenSearchEmbeddingStore(serverUrl, serviceName, region, options, indexName);
            }
            return new OpenSearchEmbeddingStore(serverUrl, apiKey, userName, password, indexName);
        }

    }

    @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<String> addAll(List<Embedding> embeddings) {
        List<String> ids = embeddings.stream()
                .map(ignored -> randomUUID())
                .collect(toList());
        addAllInternal(ids, embeddings, null);
        return ids;
    }

    @Override
    public List<String> addAll(List<Embedding> embeddings, List<TextSegment> embedded) {
        List<String> ids = embeddings.stream()
                .map(ignored -> randomUUID())
                .collect(toList());
        addAllInternal(ids, embeddings, embedded);
        return ids;
    }

    /**
     * This implementation uses the exact k-NN with scoring script to calculate
     * See https://opensearch.org/docs/latest/search-plugins/knn/knn-score-script/
     */
    @Override
    public List<EmbeddingMatch<TextSegment>> findRelevant(Embedding referenceEmbedding, int maxResults, double minScore) {
        List<EmbeddingMatch<TextSegment>> matches;
        try {
            ScriptScoreQuery scriptScoreQuery = buildDefaultScriptScoreQuery(referenceEmbedding.vector(), (float) minScore);
            SearchResponse<Document> response = client.search(
                    SearchRequest.of(s -> s.index(indexName)
                            .query(n -> n.scriptScore(scriptScoreQuery))
                            .size(maxResults)),
                    Document.class
            );
            matches = toEmbeddingMatch(response);
        } catch (IOException ex) {
            log.error("[I/O OpenSearch Exception]", ex);
            throw new OpenSearchRequestFailedException(ex.getMessage());
        }
        return matches;
    }

    private ScriptScoreQuery buildDefaultScriptScoreQuery(float[] vector, float minScore) throws JsonProcessingException {

        return ScriptScoreQuery.of(q -> q.minScore(minScore)
                .query(Query.of(qu -> qu.matchAll(m -> m)))
                .script(s -> s.inline(InlineScript.of(i -> i
                        .source("knn_score")
                        .lang("knn")
                        .params("field", JsonData.of("vector"))
                        .params("query_value", JsonData.of(vector))
                        .params("space_type", JsonData.of("cosinesimil")))))
                .boost(0.5f));

        // ===> From the OpenSearch documentation:
        // "Cosine similarity returns a number between -1 and 1, and because OpenSearch
        // relevance scores can't be below 0, the k-NN plugin adds 1 to get the final score."
        // See https://opensearch.org/docs/latest/search-plugins/knn/knn-score-script
        // Thus, the query applies a boost of `0.5` to keep score in the range [0, 1]
    }

    private void addInternal(String id, Embedding embedding, TextSegment embedded) {
        addAllInternal(singletonList(id), singletonList(embedding), embedded == null ? null : singletonList(embedded));
    }

    private void addAllInternal(List<String> ids, List<Embedding> embeddings, List<TextSegment> embedded) {<FILL_FUNCTION_BODY>


        if (isNullOrEmpty(ids) || isNullOrEmpty(embeddings)) {
            log.info("[do not add empty embeddings to opensearch]");
            return;
        }

        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");

        try {
            createIndexIfNotExist(embeddings.get(0).dimension());
            bulk(ids, embeddings, embedded);
        } catch (IOException ex) {
            log.error("[I/O OpenSearch Exception]", ex);
            throw new OpenSearchRequestFailedException(ex.getMessage());
        }
    
<no_super_class>