This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 750551f3 [integrations][vector-store][java] Apply Elasticsearch KNN 
filters before selecting neighbors (#1001)
750551f3 is described below

commit 750551f3c5a13aa8c67cfe5b6f173b6f46ee0e60
Author: Weiqing Yang <[email protected]>
AuthorDate: Thu Aug 13 21:09:10 2026 -0700

    [integrations][vector-store][java] Apply Elasticsearch KNN filters before 
selecting neighbors (#1001)
    
    Generated-by: Claude Code 2.1.228 (Claude Opus 5)
---
 docs/content/docs/development/vector_stores.md     |  2 +-
 .../elasticsearch/ElasticsearchVectorStore.java    | 40 ++++++++++++++-------
 .../ElasticsearchVectorStoreTest.java              | 42 ++++++++++++++++++++++
 3 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/docs/content/docs/development/vector_stores.md 
b/docs/content/docs/development/vector_stores.md
index 07d5eafa..f4462a07 100644
--- a/docs/content/docs/development/vector_stores.md
+++ b/docs/content/docs/development/vector_stores.md
@@ -800,7 +800,7 @@ Elasticsearch is currently supported in the Java API only. 
To use Elasticsearch
 | `dims`            | int  | `768`                     | Vector dimensionality 
                                             |
 | `k`               | int  | None                      | Number of nearest 
neighbors to return; can be overridden per query |
 | `num_candidates`  | int  | None                      | Candidate set size 
for ANN search; can be overridden per query     |
-| `filter_query`    | str  | None                      | Raw JSON 
Elasticsearch filter query (DSL) applied as a post-filter |
+| `filter_query`    | str  | None                      | Raw JSON 
Elasticsearch filter query (DSL) restricting which documents a KNN query can 
match |
 | `host`            | str  | `"http://localhost:9200"` | Elasticsearch 
endpoint                                             |
 | `hosts`           | str  | None                      | Comma-separated list 
of Elasticsearch endpoints                    |
 | `username`        | str  | None                      | Username for basic 
authentication                                  |
diff --git 
a/integrations/vector-stores/elasticsearch/src/main/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStore.java
 
b/integrations/vector-stores/elasticsearch/src/main/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStore.java
index 424c9d39..5d66e479 100644
--- 
a/integrations/vector-stores/elasticsearch/src/main/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStore.java
+++ 
b/integrations/vector-stores/elasticsearch/src/main/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStore.java
@@ -82,8 +82,8 @@ import java.util.*;
  *   <li>{@code k} (optional): Number of nearest neighbors to return; can be 
overridden per query.
  *   <li>{@code num_candidates} (optional): Candidate set size for ANN search; 
can be overridden per
  *       query.
- *   <li>{@code filter_query} (optional): A raw JSON Elasticsearch filter 
query (DSL) that is
- *       applied as a post-filter; can be overridden per query.
+ *   <li>{@code filter_query} (optional): A raw JSON Elasticsearch filter 
query (DSL) restricting
+ *       which documents a KNN query can match; can be overridden per query.
  *   <li>{@code host} or {@code hosts} (optional): Elasticsearch endpoint(s). 
If omitted, defaults
  *       to {@code localhost:9200}.
  *   <li>Authentication (optional): Either basic auth via {@code 
username}/{@code password}, or API
@@ -572,7 +572,9 @@ public class ElasticsearchVectorStore extends 
BaseVectorStore
      *
      * <p>The method prepares a KNN search request using the supplied {@code 
embedding} and merges
      * default arguments from the store with the provided {@code args}. 
Optional filter queries
-     * (JSON DSL) are applied as a post filter.
+     * (JSON DSL) restrict the documents the KNN search may match, so the 
nearest neighbours are
+     * selected from among the matching documents rather than filtered out 
afterwards. Up to {@code
+     * k} matching documents are returned even when the closest vectors 
overall do not match.
      *
      * @param embedding The embedding vector to search with
      * @param limit Maximum number of items the caller is interested in; used 
as a fallback for
@@ -603,20 +605,32 @@ public class ElasticsearchVectorStore extends 
BaseVectorStore
             List<Float> queryVector = new ArrayList<>(embedding.length);
             for (float v : embedding) queryVector.add(v);
 
+            final String finalCombined = combined;
             SearchRequest.Builder builder =
                     new SearchRequest.Builder()
                             .index(index)
                             .knn(
-                                    kb ->
-                                            kb.field(this.vectorField)
-                                                    .queryVector(queryVector)
-                                                    .k(k)
-                                                    
.numCandidates(numCandidates));
-
-            if (combined != null) {
-                final String finalCombined = combined;
-                builder = builder.postFilter(f -> f.withJson(new 
StringReader(finalCombined)));
-            }
+                                    kb -> {
+                                        kb.field(this.vectorField)
+                                                .queryVector(queryVector)
+                                                .k(k)
+                                                .numCandidates(numCandidates);
+                                        // Filter inside the KNN clause rather 
than after it, so the
+                                        // k nearest neighbours are chosen 
from the documents that
+                                        // match. A post-filter can only 
discard hits the vector
+                                        // search already picked, which yields 
fewer than k results
+                                        // whenever the nearest vectors belong 
to filtered-out
+                                        // documents.
+                                        if (finalCombined != null) {
+                                            kb.filter(
+                                                    f ->
+                                                            f.withJson(
+                                                                    new 
StringReader(
+                                                                            
finalCombined)));
+                                        }
+                                        return kb;
+                                    });
+
             final SearchResponse<Map<String, Object>> searchResponse =
                     (SearchResponse) this.client.search(builder.build(), 
Map.class);
 
diff --git 
a/integrations/vector-stores/elasticsearch/src/test/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStoreTest.java
 
b/integrations/vector-stores/elasticsearch/src/test/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStoreTest.java
index e89365e1..2b281483 100644
--- 
a/integrations/vector-stores/elasticsearch/src/test/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStoreTest.java
+++ 
b/integrations/vector-stores/elasticsearch/src/test/java/org/apache/flink/agents/integrations/vectorstores/elasticsearch/ElasticsearchVectorStoreTest.java
@@ -191,6 +191,48 @@ public class ElasticsearchVectorStoreTest {
         ((CollectionManageableVectorStore) store).deleteCollection(name);
     }
 
+    @Test
+    public void testQueryEmbeddingFiltersBeforeSelectingNeighbors() throws 
Exception {
+        // Contract: filters restrict the candidate set of the KNN search 
itself, so a matching
+        // document is returned even when it is not among the k nearest 
vectors overall. Applying
+        // the filter after the KNN phase instead would return nothing here, 
because the k nearest
+        // vectors all belong to the other user.
+        String name = "knn_prefilter";
+        ((CollectionManageableVectorStore) 
store).createCollectionIfNotExists(name, Map.of());
+
+        List<Document> docs = new ArrayList<>();
+        // Six documents pointing the same way as the query vector, none of 
them alice's.
+        for (int i = 0; i < 6; i++) {
+            Document bob =
+                    new Document("bob document " + i, Map.of("user_id", 
"bob"), "doc_bob_" + i);
+            bob.setEmbedding(new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f});
+            docs.add(bob);
+        }
+        // Three alice documents pointing orthogonally, so they never make the 
unfiltered top k.
+        for (int i = 0; i < 3; i++) {
+            Document alice =
+                    new Document(
+                            "alice document " + i, Map.of("user_id", "alice"), 
"doc_alice_" + i);
+            alice.setEmbedding(new float[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f});
+            docs.add(alice);
+        }
+        store.addEmbedding(docs, name, Collections.emptyMap());
+        Thread.sleep(1000);
+
+        List<Document> alice =
+                store.queryEmbedding(
+                        new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
+                        5,
+                        name,
+                        Map.of("user_id", "alice"),
+                        Collections.emptyMap());
+
+        Assertions.assertEquals(3, alice.size());
+        Assertions.assertTrue(alice.stream().allMatch(d -> 
d.getId().startsWith("doc_alice_")));
+
+        ((CollectionManageableVectorStore) store).deleteCollection(name);
+    }
+
     @Test
     public void testUpdateOverwritesExistingDocument() throws Exception {
         // ES bulk index is upsert by id — update should rewrite the doc in 
place.

Reply via email to