For AI agents: this page is also available as Markdown at /docs/multi-vector-fields.md. Every docs page is: add .md to its URL. llms.txt lists them all.
Multi-vector fields
A long document rarely fits one embedding well. A multi-vector field lets one document hold many vectors, typically one per passage or chunk, and search returns one hit per document, scored by the single vector that matches your query best.
#Define a multi-vector field
Availability: the search engine supports several vectors per document, but the public API has not been verified end to end for it. Mappings created through the Elasticsearch-compatible API do not set
multi, anddense_vectorarrays of arrays are not covered by the compatibility notes. Until this is confirmed in Feature status, model chunks as one document per chunk with a shared parent id field, and usecollapseon that field to get one hit per parent. The engine-level behavior is described below.
Set multi: true on a vector field, or name the field with the _vs suffix:
{
"fields": {
"passages_vs": {
"type": "vector",
"dims": 768,
"metric": "cosine",
"multi": true
}
}
}
As with any vector field, a _vs field is storage only until you give it a metric, so define dims
and metric explicitly. Everything in Vector search applies: exact and
approximate search, filters, similarity and the tuning knobs.
#Write the vectors
Send an array of arrays. A bare array is accepted as a list of one vector.
{
"id": "doc-17",
"title_t": "Shipping and returns policy",
"passages_vs": [
[0.12, 0.40, 0.05],
[0.33, 0.21, 0.58],
[0.09, 0.71, 0.14]
]
}
(The vectors here have 3 components to keep the example short. Yours must match the field's dims.)
Every vector must have the same dimension, and every element must be a finite float32. Replacing a document replaces all of its vectors. Each vector counts toward index size, so a document with 50 chunks costs about 50 times the storage of a single-vector document. See Ingesting documents for batching.
#Query
You query a multi-vector field exactly like a single-vector one, with one query vector:
{
"query": {
"knn": {
"field": "passages_vs",
"query": [0.30, 0.25, 0.55],
"k": 10
}
},
"filter": ["lang_s:en"],
"limit": 10,
"get_scores": true,
"fields": ["id", "title_t"]
}
Scoring is the maximum over a document's vectors: WarpSearch compares your query with every vector
the document holds and keeps the best similarity. The result has one hit per document, however many
of its vectors were close. k counts documents, not vectors.
Thresholds follow the same rule. With similarity, a document stays if its best vector passes. See
Similarity thresholds.
#Chunking pattern for long documents
The usual recipe has three parts:
- Split the document into passages. Pick a size your embedding model handles well, often a few hundred tokens, with a little overlap between neighbors.
- Embed each passage and write all of them into one multi-vector field on the document.
- Search by the query's embedding. The best passage decides the document's rank.
Keep the passage text out of the vector field. If you want to show the matching passage, store the chunk text yourself and look it up after the search. There are two common layouts:
| Layout | How it works | Use it when |
|---|---|---|
| One document, many vectors | A _vs field on the parent document. Chunk text lives in your own storage, keyed by document id. |
You only need the parent document back and the best chunk's position is enough. |
| One document per chunk | Each chunk is a document with a parent_id_s field, its own text and a single vector. |
You need the matching chunk text in the response, or per-chunk filters. |
A search returns the score, not which vector won. If you need to display the winning passage, the second layout is the simplest because the hit is the chunk itself.
#Parent and child style with groups
With one document per chunk, you can get one result per parent by collapsing on the parent id and asking kNN for groups:
{
"query": {
"knn": {
"field": "chunk_v",
"query": [0.30, 0.25, 0.55],
"k": 10,
"unit": "groups"
}
},
"collapse": "parent_id_s",
"limit": 10,
"get_scores": true,
"fields": ["id", "parent_id_s", "chunk_t"]
}
k now selects up to 10 parents. Several close chunks from the same parent no longer use up other
parents' places, and the hit shown for each parent is its best chunk, which carries the text you
wanted. A group's score is its best member's score. Filters and deletes apply before groups are chosen.
If you combine a multi-vector field with groups, WarpSearch first takes each document's best vector and then lets documents compete inside their group. See Nearest groups and the collapse section of Searching.
#Multi-vector search in hybrid queries
A multi-vector kNN query is an ordinary source in hybrid fusion. Put it next to a keyword query and fuse the two lists. See Hybrid search.
#Late interaction (MaxSim)
Models such as ColBERT and ColPali embed every token of a text or every patch of an image, and score a document by late interaction. For each query token vector, find the best matching document vector, then sum those best scores over all query tokens. That is called summed MaxSim, and it is different from the best single vector scoring on this page.
Coming soon: late interaction scoring with summed MaxSim is in development. We give no dates, and details may change before release.
What is being built, so you can plan around it:
- An opt-in scoring mode for multi-valued vector fields. The default stays best single vector.
- A query that carries many token vectors instead of one, up to 1,024 of them.
- Scoring under the field's metric:
sum over query vectors of (max over document vectors of similarity). - Brute-force scoring over the documents your filters select, with no approximate index for these fields. That suits small collections or a filtered candidate set, for example a re-ranking step after a first-stage search has narrowed the candidates. It has not been benchmarked at scale.
- Grouped kNN (
unit: "groups") is not planned to combine with several query vectors in the first version.
Until it ships, you can approximate late interaction in your own code: fetch candidates with the best single vector search, then compute MaxSim in your application on vectors you hold. The same advice applies to any reranking, see Hybrid search.
#Limits and tips
- A multi-valued vector field cannot be a collapse key.
- Scores are best-single-vector scores. Documents with many vectors are not penalized or favored by how many they hold, but a document with more chunks has more chances to match by luck. Keep chunk sizes consistent.
- Search cost grows with the total number of vectors, not the number of documents. Approximate indexes help once segments are large enough, see When indexes get built.
- To reduce storage for many-chunk collections, consider
truncate_dimswith an MRL model.