Skip to content

Guides

Vector and hybrid search

Declare vector fields and embedding profiles in a schema, then run vector, hybrid, and exhaustive queries with explicit candidate work.


An index schema may declare several vector fields. Each field pins its own dimension, similarity, normalization rule, source-field recipe, and embedding profile, so a 384-dimensional semantic field and a 768-dimensional visual field can coexist in one index without sharing an embedding space. A query selects exactly one field.

Declaring a vector field

A vector field is a repeated float with a vector option, and the embedding profile it names is declared on the message:

proto
syntax = "proto3";
package cargo;

import "yolosearch/v1/schema.proto";
import "google/protobuf/timestamp.proto";

message Article {
  option (yolosearch.v1.document) = {
    index: "articles"
    embedding_profiles: {
      name: "semantic-gte-small"
      mode: EMBEDDING_MODE_EXTERNAL
      model: "gte-small"
      revision: "2026-08-31"
      dimensions: 384
      pooling: "mean"
      normalize: true
      maximum_tokens: 512
      endpoint: "http://embeddings.internal:8000"
      credential_environment: "YOLOSEARCH_EMBEDDING_CREDENTIAL"
    }
  };

  string url_hash = 1 [(yolosearch.v1.field) = { key: true }];
  string title    = 2 [(yolosearch.v1.field) = { indexed: true, stored: true, weight: 2.0 }];
  string headline = 3 [(yolosearch.v1.field) = { indexed: true, stored: true, weight: 2.0 }];
  string excerpt  = 4;

  repeated float semantic_vector = 5 [(yolosearch.v1.field) = { vector: {
    dimensions: 384
    similarity: VECTOR_SIMILARITY_COSINE
    embedding_profile: "semantic-gte-small"
    source_fields: "title"
    source_fields: "headline"
    source_fields: "excerpt"
    normalize: true
  }}];
}

The vector field's own options:

Option Meaning
dimensions the vector length this field accepts
similarity VECTOR_SIMILARITY_COSINE, VECTOR_SIMILARITY_DOT_PRODUCT, or VECTOR_SIMILARITY_EUCLIDEAN
embedding_profile the name of a profile declared on the message
source_fields the ordered fields the builder embeds when no vector was supplied
required whether a document must carry or produce a vector
normalize normalize the vector before it is stored

A document may supply the field explicitly in JSON as an array of numbers. Supplied vectors override automatic embedding. When the field is absent, the builder embeds the ordered source_fields before atomically accepting the batch — so the acknowledgment you get back already accounts for the embedding work.

Apply and load exactly as for any other schema; see schemas and ingest.

The four provider modes

Text embedding has three provider modes, plus a mode for vectors you compute yourself:

mode Where vectors come from
EMBEDDING_MODE_SUPPLIED_ONLY the documents and queries supply them; the server embeds nothing
EMBEDDING_MODE_INTERNAL the pure-Go runtime, from a manifest the profile pins
EMBEDDING_MODE_EXTERNAL the profile's OpenAI-compatible HTTP endpoint
EMBEDDING_MODE_GRPC the generic Moltavista embedder service

EXTERNAL

Calls the profile's OpenAI-compatible endpoint. Plain HTTP is refused unless embedding.external_allow_http=true, which exists for a trusted development LAN and not for anything else. The profile stores only the name of a credential environment variable — credential_environment above. The token itself is never in the schema, the command line, the logs, or a receipt.

zsh
export YOLOSEARCH_EMBEDDING_CREDENTIAL='…'
yolosearch serve --data-dir ./ys --embedding-external-allow-http

INTERNAL

Uses the pure-Go runtime when embedding.internal_enabled=true. A profile pins a manifest_uri and its SHA-256, and every artifact is size- and digest-verified before installation into the separate model cache at embedding.model_cache_dir. No Python, shared library, repository code, custom operator, or tokenizer plugin is executed. Setting embedding.model_cache_dir to the empty string disables internal embedding.

GRPC

Calls the generic Moltavista embedder service. The schema pins the logical model name, the revision, and a 32-byte model_fingerprint_sha256, while the endpoint stays routing metadata. The fingerprint is part of the vector-space identity: changing the model package cannot silently reuse old vectors or cached query embeddings.

Because model_fingerprint_sha256 is a protobuf bytes field, it is base64 in protobuf JSON. Queries are sent as interactive work and indexing batches as bulk work when embedding.grpc_work_class=auto. Plaintext cluster endpoints require embedding.grpc_allow_insecure=true; production endpoints default to TLS. A dns:///host:port endpoint lets the process-wide connection use gRPC round-robin balancing across all service addresses.

Querying

A query names one vector field and gives either text to embed or a vector to use directly.

Text

zsh
yolosearch search articles --vector-field semantic_vector \
  --vector-text 'Roger Federer' --top-k 30 --ids

The coordinator embeds the text once, then sends the canonical vector to the segment executors and workers. That matters on a fleet: the embedding cost is paid once per query, not once per segment.

Hybrid

zsh
yolosearch search articles 'title:federer' \
  --vector-field semantic_vector --vector-text 'Roger Federer tennis' \
  --probes 16 --candidates 1000 \
  --lexical-weight 0.5 --vector-weight 1.0 --fusion weighted

A hybrid query is an ordinary lexical query plus a vector clause. The lexical query is lowered by whichever grammar --dialect selects — see Lucene and CQP — and filters apply as usual, so filters and projection still holds.

A supplied vector

zsh
yolosearch search articles --vector-field semantic_vector \
  --vector-file query-vector.json --exhaustive --top-k 20

The file is a JSON float array, bounded to 16 MiB before decoding. Use --vector-file - to read it from standard input.

The flags

Flag Default Meaning
--vector-field — the vector field used for vector or hybrid search
--vector-text — embed this text with the selected field's schema profile
--vector-file — read a JSON float array from a file, or - for stdin
--probes 0 IVF lists to probe; zero uses the server default
--candidates 0 ANN candidates to exact-rerank; zero uses the server default
--exhaustive false scan every covered vector instead of IVF-PQ candidate generation
--fusion weighted hybrid fusion: weighted or rrf
--lexical-weight 1 lexical score weight for hybrid search
--vector-weight 1 vector score weight
--tail exact tail ordering: exact or banded
--maximum-ordering-error 0 the largest score inversion the caller accepts in a banded tail
--score-ranges false include conservative score ranges for an approximate tail

--fusion rrf selects reciprocal-rank fusion, which combines the two rankings by position rather than by score and so needs no weight calibration between two differently-scaled scores.

--probes and --candidates are the two knobs that trade recall against work. --exhaustive removes the candidate-generation step entirely and is the form to use when establishing a recall baseline for a tuned configuration.

Approximate tails

--tail banded opts into a bounded approximate lexical tail. --maximum-ordering-error states the largest score inversion the caller will accept, and --score-ranges asks for conservative ranges alongside the scores.

An unsupported or too-tight request falls back to exact ordering, and the response header and trailer report the resulting ordering. Check those fields to determine which ordering the response uses. See ranking and exactness for the guarantee this preserves.

Caching and identity

The process owns one provider manager and one query-vector cache across every hosted index. A repeated text query is keyed by embedding fingerprint plus a SHA-256 of the text; the raw query text is not retained in the cache key.

The cache is bounded by three settings:

Setting Default
embedding.query_cache_entries 4096
embedding.query_cache_bytes 64 MiB
embedding.query_cache_ttl 10m

Including the embedding fingerprint in the key is what makes a model change safe: a new fingerprint cannot hit an entry produced by the old model.

ID-only results

--ids remains the cheapest large-result output on a vector query for the same reason it is on a lexical one: it requests neither scores nor stored documents.

zsh
yolosearch search articles --vector-field semantic_vector \
  --vector-text 'cold cache hydration' --top-k 500 --ids

Next