Vector schema annotations

Describe vector columns with VectorSchema and VectorSchemaProvider. Covers the three ways to size a vector field (an embedder from the context, an embedder instance, an explicit schema) and MultiVectorSchema for models like ColBERT.

Version
v 1.0.14
Last reviewed
Nov 4, 2026
ExperimentalThe Rust SDK is experimental. It is outside CocoIndex's compatibility promise: any release, including a minor one, may change or remove its API. It is not yet published on crates.io.

The schema module (cocoindex::resources::schema, re-exported from cocoindex::prelude) defines types that describe vector columns. CocoIndex connectors use these to automatically configure the correct column type (e.g., vector(384) in Postgres, fixed_size_list<float32>(384) in LanceDB).

Different from Python

A vector schema is not attached to the row type with an annotation. A row struct field is marked #[coco(vector)] (or #[coco(vector = N)]), and the connector’s TableSchema resolves its dimension.

VectorSchema

A struct that describes a vector column’s dtype and dimension.

use cocoindex::prelude::*;

let schema = VectorSchema::f32(768);
// A half-precision column, where the store has one (halfvec in Postgres, float16 in LanceDB).
let half = VectorSchema::f16(768);
// The same, written out.
let schema = VectorSchema { element_type: VectorElementType::Float32, size: 768 };

Fields:

  • element_type — A VectorElementType: Float32, Float16 or Float64. Float16 selects a half-precision column where the store has one; Float32 and Float64 both select the store’s standard float vector column.
  • size — Number of dimensions in the vector (e.g., 384)

You can construct VectorSchema directly when using a custom embedding model that doesn’t implement VectorSchemaProvider:

use cocoindex::connectors::qdrant::{self, CollectionSchema, Distance};
use cocoindex::prelude::*;

cocoindex::context_key!(static DB: qdrant::QdrantConnection);

// For a custom CLIP model with known dimension
let schema = VectorSchema::f32(768);

// Use it in a Qdrant collection schema
let target = qdrant::mount_collection_target(
    &ctx,
    &DB,
    "image_search",
    CollectionSchema::from_vector_schema(schema, Distance::Cosine)?,
)
.await?;

VectorSchemaProvider

A trait for values that can provide vector schema information, with one method, async fn vector_schema(&self) -> Result<VectorSchema>. The built-in SentenceTransformerEmbedder, ApiEmbedder and VectorSchema itself implement it.

A row struct does not carry the provider: a field marked #[coco(vector)] has a dimension that is “unresolved” until the connector schema’s with_vector_schema(field, &provider) (or with_vector_dim(field, n)) supplies it. Mounting a target with an unresolved vector column is an error that names the field and with_vector_dim.

There are three ways to size a vector field:

Define a context key for the embedder, resolve it with ctx.get_key(&EMBEDDER)? when you mount the target, and pass the embedder to with_vector_schema. This is the recommended approach because the embedder is configured once in the lifespan and shared across all functions via context.

use cocoindex::connectors::postgres;
use cocoindex::ops::sentence_transformers::SentenceTransformerEmbedder;
use cocoindex::prelude::*;

const EMBED_MODEL: &str = "sentence-transformers/all-MiniLM-L6-v2";

cocoindex::context_key!(
    static EMBEDDER: SentenceTransformerEmbedder,
    detect_change
);

#[derive(Clone, Serialize, Deserialize, SchemaFields)]
struct DocEmbedding {
    id: i64,
    text: String,
    #[coco(vector)]
    embedding: Vec<f32>,
}

// In the lifespan, provide the embedder:
#[cocoindex::lifespan]
async fn lifespan(builder: EnvironmentBuilder) -> Result<EnvironmentBuilder> {
    let embedder = SentenceTransformerEmbedder::load(EMBED_MODEL).await?;
    Ok(builder.provide_key(&EMBEDDER, embedder))
}

// When mounting the target, the embedder reports the dimension of the `embedding` column:
let table = postgres::mount_table_target(
    &ctx,
    &DB,
    "doc_embeddings",
    postgres::TableSchema::from_row::<DocEmbedding>(["id"])?
        .with_vector_schema("embedding", ctx.get_key(&EMBEDDER)?)
        .await?,
    Some("public"),
)
.await?;

// In functions, access the embedder:
let embedding = ctx.get_key(&EMBEDDER)?.embed(ctx, text).await?;

Using a VectorSchemaProvider instance

Pass an embedder instance directly to with_vector_schema. Simpler for scripts where the embedder is a module-level constant.

let embedder = SentenceTransformerEmbedder::load("sentence-transformers/all-MiniLM-L6-v2").await?;

let schema = postgres::TableSchema::from_row::<DocEmbedding>(["id"])?
    .with_vector_schema("embedding", &embedder) // dimension read from the model (384)
    .await?;

with_vector_schema takes any VectorSchemaProvider by reference. The Postgres, SQLite, Doris, LanceDB and SurrealDB schemas have it; Qdrant’s CollectionSchema::from_row, Turbopuffer and zvec take with_vector_dim.

Using a VectorSchema

Specify dimension and dtype explicitly. Useful when using a custom embedding model that doesn’t implement VectorSchemaProvider.

#[derive(Clone, Serialize, Deserialize, SchemaFields)]
struct ImageEmbedding {
    id: i64,
    #[coco(vector = 768)]
    embedding: Vec<f32>,
}

#[coco(vector = N)] fixes an f32 column of dimension N for every connector the struct is used with. #[coco(vector = N, half)] makes it a half-precision column. For a dimension known only at run time, keep #[coco(vector)] and call with_vector_dim("embedding", n)? on the connector schema, or pass a fixed VectorSchema to with_vector_schema, since VectorSchema is its own provider.

A connector’s TableSchema::from_row::<Row>(primary_key) reads the #[derive(SchemaFields)] row struct. A #[coco(vector = N)] field is complete; a #[coco(vector)] field waits for with_vector_schema or with_vector_dim.

MultiVectorSchema / MultiVectorSchemaProvider

Analogous types for multi-vector columns (e.g., ColBERT-style token-level embeddings). MultiVectorSchema wraps a VectorSchema describing the individual vectors. Used by connectors like Qdrant that support multi-vector storage.

use cocoindex::connectors::qdrant::{CollectionSchema, Distance};
use cocoindex::prelude::*;

let multi_schema = MultiVectorSchema {
    vector_schema: VectorSchema::f32(128),
};
let collection = CollectionSchema::from_multivector_schema(multi_schema, Distance::Cosine)?;

MultiVectorSchemaProvider is the trait form (async fn multi_vector_schema(&self) -> Result<MultiVectorSchema>), implemented by MultiVectorSchema itself; Qdrant’s CollectionSchema::from_multivector_provider(&provider, distance) accepts one.