From d9b4850eb6c71c3d020d714245921b5accbfc92f Mon Sep 17 00:00:00 2001 From: chabinhwang <7chabin@gmail.com> Date: Mon, 25 May 2026 23:11:16 +0900 Subject: [PATCH] Reduce allocation while preserving Typesense vector queries Build the vector query directly from the embedding array so search behavior stays identical without boxing floats into a temporary list. Constraint: Spring AI pull requests require DCO sign-off and focused reviewable changes Rejected: Changing Typesense query semantics | performance-only PR should preserve the request format Confidence: high Scope-risk: narrow Directive: Keep vector query formatting compatible with existing Typesense requests Tested: ./mvnw -pl vector-stores/spring-ai-typesense-store -Dtest=TypesenseVectorStoreTests test; ./mvnw -pl vector-stores/spring-ai-typesense-store package; git diff --check Not-tested: Full repository ./mvnw package Signed-off-by: chabinhwang <7chabin@gmail.com> --- .../typesense/TypesenseVectorStore.java | 23 +++++++--- .../typesense/TypesenseVectorStoreTests.java | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreTests.java diff --git a/vector-stores/spring-ai-typesense-store/src/main/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStore.java b/vector-stores/spring-ai-typesense-store/src/main/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStore.java index e5287ab5dc..03dbc810e1 100644 --- a/vector-stores/spring-ai-typesense-store/src/main/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStore.java +++ b/vector-stores/spring-ai-typesense-store/src/main/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStore.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.Optional; import java.util.stream.IntStream; -import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -236,11 +235,8 @@ public List doSimilaritySearch(SearchRequest request) { multiSearchCollectionParameters.collection(this.collectionName); multiSearchCollectionParameters.q("*"); - Stream floatStream = IntStream.range(0, embedding.length).mapToObj(i -> embedding[i]); // typesense uses only cosine similarity - String vectorQuery = EMBEDDING_FIELD_NAME + ":(" + "[" - + String.join(",", floatStream.map(String::valueOf).toList()) + "], " + "k: " + request.getTopK() + ", " - + "distance_threshold: " + (1 - request.getSimilarityThreshold()) + ")"; + String vectorQuery = buildVectorQuery(embedding, request.getTopK(), request.getSimilarityThreshold()); multiSearchCollectionParameters.vectorQuery(vectorQuery); multiSearchCollectionParameters.filterBy(nativeFilterExpressions); @@ -280,6 +276,23 @@ public List doSimilaritySearch(SearchRequest request) { } } + static String buildVectorQuery(float[] embedding, int topK, double similarityThreshold) { + StringBuilder vectorQueryBuilder = new StringBuilder(EMBEDDING_FIELD_NAME.length() + embedding.length * 8 + 64); + vectorQueryBuilder.append(EMBEDDING_FIELD_NAME).append(":(["); + for (int i = 0; i < embedding.length; i++) { + if (i > 0) { + vectorQueryBuilder.append(','); + } + vectorQueryBuilder.append(embedding[i]); + } + return vectorQueryBuilder.append("], k: ") + .append(topK) + .append(", distance_threshold: ") + .append(1 - similarityThreshold) + .append(')') + .toString(); + } + int embeddingDimensions() { if (this.embeddingDimension != INVALID_EMBEDDING_DIMENSION) { return this.embeddingDimension; diff --git a/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreTests.java b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreTests.java new file mode 100644 index 0000000000..9a5d190e83 --- /dev/null +++ b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023-present the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.springframework.ai.vectorstore.typesense; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TypesenseVectorStore}. + * + * @author chabinhwang + */ +class TypesenseVectorStoreTests { + + @Test + void buildVectorQuery() { + String vectorQuery = TypesenseVectorStore.buildVectorQuery(new float[] { 0.1f, -2.5f, 3.0E-4f }, 7, 0.75); + + assertThat(vectorQuery).isEqualTo("embedding:([0.1,-2.5,3.0E-4], k: 7, distance_threshold: 0.25)"); + } + + @Test + void buildVectorQueryWithEmptyEmbedding() { + String vectorQuery = TypesenseVectorStore.buildVectorQuery(new float[0], 3, 0.5); + + assertThat(vectorQuery).isEqualTo("embedding:([], k: 3, distance_threshold: 0.5)"); + } + +}