Grouped document search returns the top-K most similar documents from each group, where groups are defined by the values of a specified field. This is useful in Retrieval-Augmented Generation (RAG) pipelines: when a long document is split into chunks and stored as separate vectors, a standard similarity search may return multiple chunks from the same source document, leaving other relevant documents unrepresented. Grouped search solves this by returning the best-matching chunks per document, giving you broader coverage across your corpus.
Prerequisites
Before you begin, make sure you have:
A cluster. See Create a cluster.
An API key. See Manage API keys.
The latest version of the DashVector SDK for Java installed. See Install DashVector SDK.
API definition
queryGroupBy is defined on the DashVectorCollection class and is available in both synchronous and asynchronous forms:
// Synchronous
public Response<List<Group>> queryGroupBy(QueryDocGroupByRequest queryDocGroupByRequest);
// Asynchronous
public ListenableFuture<Response<List<Group>>> queryGroupByAsync(QueryDocGroupByRequest queryDocGroupByRequest)Examples
ReplaceYOUR_API_KEYwith your API key andYOUR_CLUSTER_ENDPOINTwith your cluster endpoint before running the examples.
All examples use QueryDocGroupByRequest.builder() to construct the request and call collection.queryGroupBy() to run the search.
Search by vector
This example creates a collection, inserts documents with document_id and chunk_id fields, and runs a grouped search using a query vector. The results are grouped by document_id, returning up to 2 groups with up to 3 documents each.
package com.aliyun.dashvector;
import com.aliyun.dashvector.models.Doc;
import com.aliyun.dashvector.models.Group;
import com.aliyun.dashvector.models.Vector;
import com.aliyun.dashvector.models.requests.CreateCollectionRequest;
import com.aliyun.dashvector.models.requests.InsertDocRequest;
import com.aliyun.dashvector.models.requests.QueryDocGroupByRequest;
import com.aliyun.dashvector.models.responses.Response;
import com.aliyun.dashvector.proto.CollectionInfo;
import com.aliyun.dashvector.proto.FieldType;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class Main {
public static void main(String[] args) {
DashVectorClientConfig config =
DashVectorClientConfig.builder()
.apiKey("YOUR_API_KEY")
.endpoint("YOUR_CLUSTER_ENDPOINT")
.timeout(30f)
.build();
DashVectorClient client = new DashVectorClient(config);
CreateCollectionRequest request = CreateCollectionRequest.builder()
.name("group_demo")
.dimension(4)
.metric(CollectionInfo.Metric.dotproduct)
.dataType(CollectionInfo.DataType.FLOAT)
.filedSchema("document_id", FieldType.STRING)
.filedSchema("chunk_id", FieldType.INT)
.build();
Response<Void> createResponse = client.create(request);
assert createResponse.isSuccess();
DashVectorCollection collection = client.get("group_demo");
assert collection.isSuccess();
List<Doc> docs = new ArrayList<>();
for (int i = 0; i < 6; i++) {
int finalI = i;
docs.add(
Doc.builder()
.id(Integer.toString(i+3))
.vector(Vector.builder().value(Collections.nCopies(4, (float) i+3)).build())
.fields(
new HashMap<String, Object>() {
{
put("document_id", "paper" + finalI % 3);
put("chunk_id", finalI);
}
}
)
.build());
}
InsertDocRequest insertRequest = InsertDocRequest.builder().docs(docs).build();
Response<Void> insertResponse = collection.insert(insertRequest);
assert insertResponse.isSuccess();
// Build a query vector.
Vector queryVector = Vector.builder().value(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f)).build();
// Build the request: group by document_id, return 2 groups with up to 3 docs each.
QueryDocGroupByRequest queryDocGroupByRequest =
QueryDocGroupByRequest.builder()
.includeVector(true)
.vector(queryVector)
.groupByField("document_id")
.groupCount(2)
.groupTopk(3)
.build();
// Run the grouped search.
Response<List<Group>> response = collection.queryGroupBy(queryDocGroupByRequest);
assert response.isSuccess();
System.out.println(response);
}
}The response groups results by document_id. Each group contains the most similar chunks from that document:
{
"code": 0,
"message": "Success",
"requestId": "47404048-6f40-47ad-9d62-2675704afb26",
"output": [
{
"groupId": "paper2",
"docs": [
{
"id": "8",
"vector": { "value": [8.0, 8.0, 8.0, 8.0] },
"fields": { "document_id": "paper2", "chunk_id": 5 },
"score": 8.0,
"sparseVector": {}
},
{
"id": "5",
"vector": { "value": [5.0, 5.0, 5.0, 5.0] },
"fields": { "document_id": "paper2", "chunk_id": 2 },
"score": 5.0,
"sparseVector": {}
}
]
},
{
"groupId": "paper1",
"docs": [
{
"id": "7",
"vector": { "value": [7.0, 7.0, 7.0, 7.0] },
"fields": { "document_id": "paper1", "chunk_id": 4 },
"score": 7.0,
"sparseVector": {}
},
{
"id": "4",
"vector": { "value": [4.0, 4.0, 4.0, 4.0] },
"fields": { "document_id": "paper1", "chunk_id": 1 },
"score": 4.0,
"sparseVector": {}
}
]
}
]
}Search by primary key
To search using the vector stored with a specific document, specify the document's primary key using id() instead of vector(). The search finds similar documents based on that stored vector and groups the results.
// Build the request: use the vector of document "1", group by age field.
QueryDocGroupByRequest request = QueryDocGroupByRequest.builder()
.groupByField("age")
.id("1") // Use the vector stored for this ID.
.groupCount(10) // Return up to 10 groups.
.groupTopk(10) // Return up to 10 documents per group.
.outputFields(Arrays.asList("name", "age")) // Return only name and age fields.
.includeVector(true)
.build();
Response<List<Group>> response2 = collection.queryGroupBy(request);
assert response2.isSuccess();Search with a filter
Add a filter() to restrict the search to documents matching a condition. The filter uses SQL WHERE clause syntax.
Vector vector = Vector.builder().value(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f)).build();
// Build the request: search by vector with a filter on the age field.
QueryDocGroupByRequest request = QueryDocGroupByRequest.builder()
.groupByField("age")
.vector(vector)
.filter("age > 18") // Only include documents where age > 18.
.outputFields(Arrays.asList("name", "age"))
.includeVector(true)
.build();
Response<List<Group>> response = collection.queryGroupBy(request);
assert response.isSuccess();For filter syntax details, see Conditional filtering.
Search with dense and sparse vectors
Combine a dense vector with a sparse vector to implement keyword-aware semantic search. You can use a sparse vector to represent the keyword weight.
For more information about keyword-aware semantic search, see Keywords-aware retrieval.
Vector vector = Vector.builder().value(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f)).build();
// Build the request: combine dense and sparse vectors for hybrid grouped search.
QueryDocGroupByRequest request = QueryDocGroupByRequest.builder()
.groupByField("age")
.vector(vector)
.sparseVector(
new Map<Integer, Float>() {
{
put(1, 0.4f);
put(10000, 0.6f);
put(222222, 0.8f);
}
})
.build();
Response<List<Group>> response = collection.queryGroupBy(request);
assert response.isSuccess();Request parameters
Build the request using QueryDocGroupByRequest.builder(). You must specify either vector() or id().
| Method | Required | Default | Description |
|---|---|---|---|
groupByField(String groupByField) | Yes | — | The field to group results by. Schema-free fields are not supported. |
groupCount(int groupCount) | No | 10 | The maximum number of groups to return. This is a best-effort parameter: the specified number of groups is returned in most cases. |
groupTopk(int groupTopk) | No | 1 | The number of similar results to return per group. This is a best-effort parameter with lower priority than groupCount. |
vector(Vector vector) | No | — | The dense query vector. |
sparseVector(Map<Integer, Float>) | No | — | The sparse query vector. |
id(String id) | No | — | The primary key of a document whose stored vector is used as the query. |
filter(String filter) | No | — | A conditional filter using SQL WHERE clause syntax. See Conditional filtering. |
includeVector(bool includeVector) | No | false | Specifies whether to include vector data in the response. |
partition(String partition) | No | default | The partition to search in. |
outputFields(List<String> outputFields) | No | — | The document fields to include in the response. All fields are returned by default. |
outputField(String field) | No | — | A single-field variant of outputFields. |
build() | — | — | Builds a QueryDocRequest object. |
Response parameters
queryGroupBy returns a Response<List<Group>> object.
| Method | Type | Description | Example |
|---|---|---|---|
getCode() | int | Status code. See Status codes. | 0 |
getMessage() | String | Status message. | success |
getRequestId() | String | Unique request ID. | 19215409-ea66-4db9-8764-26ce2eb5bb99 |
getOutput() | List<Group> | List of groups, each containing the top-K matching documents. | {"groupId": "20", "docs": [{"id": "2", "vector": {"value": [0.475044, 0.906511, 0.60797, 0.573515]}, "fields": {"name": "lisi", "weight": 45.0, "male": false, "age": 20}, "score": 0.6406033, "sparseVector": {}}]} |
isSuccess() | Boolean | Whether the request succeeded. | true |