Skip to main content
Feature Search stage showing multi-vector semantic search with fusion
The Feature Search stage is the primary search stage for retrieval pipelines. It performs vector similarity search across one or more embedding features, supporting single-modal, multimodal, and hybrid search patterns. Results from multiple searches are fused using configurable strategies (RRF, DBSF, weighted, max, or learned).
Stage Category: FILTER (Retrieves documents)Transformation: 0 documents → N documents (retrieves from collection based on vector similarity)

Run feature search on your own data

Create a managed namespace, extract embeddings from your own files, then compose this stage into a retriever.

When to Use

When NOT to Use

Core Concepts

Feature URIs

Feature URIs identify which embedding index to search. They follow the pattern:
Examples:
  • mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding - Multimodal text/image/video embeddings
  • mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1 - Text-only embeddings
  • mixpeek://image_extractor@v1/google_siglip_base_v1 - Image embeddings
  • mixpeek://multimodal_extractor@v1/multilingual_e5_large_instruct_v1 - Speech/transcript embeddings (from video/audio)

Fusion Strategies

When searching multiple features, results are combined using fusion:

Learned Fusion Configuration

When fusion is set to "learned", you can provide a learning_config object to control how the bandit adapts. See Auto-Tune for a full walkthrough.

learning_config Fields

See Auto-Tune for the full concept overview, Reward Signals for reward map customization, and Rollout & Safety for traffic splitting and kill switch details.

Parameters

Search Object Parameters

Each item in the searches array supports:

Query Input Modes

The query field on each search object accepts either a plain string (shorthand for text mode) or an object with an explicit input_mode: Text — embed a string and search:
Content — fetch a URL and embed it:
Vector — use a pre-computed embedding directly (no inference at query time):
Multi-content — embed multiple files together in one API call. Only valid when the feature_uri points to an extractor whose vector index has supports_multi_query=True (currently: gemini_multifile_extractor). Attempting this with any other feature URI returns a 400 error.
Each item in values is auto-detected: URLs (http://, https://, s3://) are fetched and embedded as files; all other strings are embedded as text. All items are passed to the underlying model in one call, producing a single query vector that mirrors how objects were indexed. Set lexical: true on a search to run keyword/BM25 matching instead of vector similarity. The query text is matched against the namespace’s full-text index — it is not embedded into a vector. BM25 catches exact tokens that dense embeddings routinely miss: brand names, SKUs, prices like $9.99, promo codes, error strings, and CTAs.
Searching only one field (e.g. OCR text). BM25 matches across all text-indexed string fields — it can’t be scoped to a single field like ocr_text. To make one field independently searchable, give it its own dense index by running a text_extractor over it (map the extractor’s input to ocr_text), then feature_search that feature URI directly. For coarse exact-substring filtering on a single field, an attribute_filter with the contains operator works but is not relevance-ranked.
The real power is hybrid retrieval — fuse a dense (vector) search with a lexical (BM25) search under rrf so semantic recall and exact-keyword precision reinforce each other:
Dense + Lexical Hybrid (RRF)
Use rrf fusion for dense+lexical hybrid — it ranks by position, so it is immune to the score-scale mismatch between cosine similarity and BM25. Avoid weighted/max here unless you have a specific reason.

Query Preprocessing

When searching with large files (videos, PDFs, long documents) as input, query_preprocessing decomposes the file into chunks using the same extractor pipeline that indexed your data, runs parallel searches for each chunk, and fuses the results. This is ingestion applied to the query — same decomposition and embedding, but vectors are used for search instead of storage.
params uses the extractor’s own parameter schema. Whatever parameters the extractor accepts during ingestion (e.g. split_method, time_split_interval for video; chunk_size, chunk_overlap for text) are the same parameters you pass here. There is no separate preprocessing-specific schema — the extractor drives the decomposition exactly as it would during collection processing. Refer to the extractor’s own documentation for valid parameter names.
You can also set query_preprocessing at the stage level (on parameters) to apply it to all searches as a default. Per-search settings override the stage default.
Aggregation strategies:

Configuration Examples

Query Preprocessing Examples

Search with a large video — decompose it into 10-second segments, search each, and fuse:
Preprocessing uses the same extractor pipeline that indexed your data. The params accept the same fields you configured on your collection’s feature extractor (e.g., split_method, chunk_size). If you don’t specify params, extractor defaults are used.
The response includes preprocessing metadata showing what happened:
Each result also includes query_chunks showing which parts of your query matched:

Grouping (Decompose/Recompose)

When documents are decomposed into chunks (e.g., video frames, document pages), use group_by to recompose results by parent:
Use cases:
  • Video search: Group frames by video, return top 3 frames per video
  • Document search: Group chunks by document, return best chunks per doc
  • Product search: Group variants by product family
Get counts of results by field values for building filter UIs. Each entry is a config object, so a bare field name is not accepted:
Facets run in parallel with the search under the same filters, and they count the entire filtered result set, not the page you asked for. So a page_size of 10 still gives counts across every match. Response. facets is an array, one entry per faceted field, each carrying its key and its value/count buckets:
Every faceted field needs a keyword index in MVS. Faceting fails without one. metadata.*, status and collection_id are commonly auto-indexed; a custom field needs indexing configured first. See payload indexes.
facets is surfaced at the top level of the execution response so a consumer has one predictable place to read it. The same data also appears per-stage under stage_statistics.stages.<stage>.metadata.facets.When more than one stage computes facets, only the last stage’s reach the top level. Read the per-stage path if you need facets from an earlier stage.The field is absent when no facets were requested. The spec types each entry as an open object, so read the bucket keys defensively.

Filter Syntax

Filtered fields must have payload indexes on your namespace. Without indexes, filtering is slow and the response includes warnings about unindexed fields.
Pre-filters use boolean logic with AND/OR/NOT. The key is pre_filters; a key named filters is rejected with a 422 naming the valid placements.
Leave searches empty and supply pre_filters to select documents by attribute alone, with no vector query and no embedding cost.
pre_filters beside searches inside parameters is the canonical placement: Mixpeek lifts it to the stage config before validation. Stage-level pre_filters, a sibling of parameters, works too and is the placement to use when a stage runs a vector query and a filter together.
pre_filters is required in this mode. An empty searches with no pre_filters gives the stage nothing to select on and returns a 400.

Supported Operators

Performance

For best performance, use pre-filters to reduce the search space. Filtering at the vector index level is much faster than post-filtering in later stages.

Common Pipeline Patterns

Basic Search + Rerank

Multimodal Search + Filter + Limit

Video Search with Frame Grouping

E-commerce Search with Facets

Output Schema

Each result includes: Example output:

Comparison: feature_search vs attribute_filter

Error Handling

The following is a complete working example of creating a retriever that uses the feature_search stage, then executing it. Pay close attention to the field names — several are easy to confuse.
Common mistakes:
  • Use collection_identifiers (not collection_ids) in the retriever body.
  • input_schema is a flat map keyed by field name ({"query": {"type": "text"}}) — do not wrap it in a JSON Schema object ({"properties": {...}, "type": "object"}).
  • Use type: "text" (not "string") in input_schema values.
  • stage_type at the outer level must be "filter" — passing stage_type: "feature_search" is rejected (feature_search is a stage_id, not a stage_type).
  • stage_id: "feature_search" lives inside the config object, not at the outer stage_id.
  • Inside each search, the query value uses {"input_mode": "text", "value": "..."} — the value key, not a bare text key.
  • final_top_k lives inside config.parameters, not at the top level.
  • If a feature_uri is wrong, the error lists the available_feature_uris for your target collections — copy the exact URI (e.g. multilingual_e5_large_instruct_v1, not embedding).

Step 1 — Create the Retriever

Step 2 — Execute the Retriever

Finding the Right feature_uri

The feature_uri must match an embedding index that exists in your namespace. To discover available feature URIs, list the vector indexes in a collection:
Each item in the vector_indexes array has a feature_uri field — use that value directly in your retriever stage.

Embedding Task Conditioning

Feature search automatically applies task-aware embedding conditioning to instruction-aware models (E5, Gemini) at query time. This means query embeddings are optimized for asymmetric retrieval without any configuration. How it works:
  • Index time: Extractors embed documents with retrieval_document task (configurable via embedding_task on the extractor — see Text Extractor)
  • Query time: Feature search automatically uses retrieval_query task for all query embeddings
This asymmetric pairing (document vs. query) improves retrieval quality by ~10% for instruction-aware models like E5-Large. The embedding_task used is included in the stage response metadata:
Task-aware models: