Embedding
Native embeddings are in private beta. Please reach out to us if you're interested. We're actively seeking new users.
Native embeddings convert your documents and queries into vectors as you read from and write to turbopuffer. You don't need to write any code to integrate with an embedding provider. Instead, turbopuffer handles embedding your data on writes and queries.
Native embeddings are stored as a vector alongside the data that was embedded. You can read and export the source data and the native embedding vectors like you can any other field. You always own the data you store in turbopuffer.
To enable native embedding, use the embed property on an attribute's schema to
specify an embedding model. Writes and queries to that attribute are turned into
vectors on the fly.
import { Turbopuffer } from "@turbopuffer/turbopuffer";
const tpuf = new Turbopuffer({
region: "gcp-us-central1", // pick a region: https://turbopuffer.com/docs/regions
});
// create a new namespace with native embeddings
const ns = tpuf.namespace(`embedding-intro-ts`);
await ns.write({
upsert_rows: [
{
id: 1,
text: "A cat sleeping on a windowsill",
category: "animal",
},
{
id: 2,
text: "A playful kitten chasing a toy",
category: "animal",
},
],
distance_metric: "cosine_distance",
schema: {
text: {
type: "string",
// use native embeddings by setting the `embed` property on a text
// attribute to your favorite model
embed: "example/random",
},
},
});Pricing
Embedding is billed based on the number of tokens embedded by a model. How your document translates into tokens is model dependent. A rough rule of thumb is that 4 bytes of English text is about 1 token. The number of tokens embedded is included in both the query and write responses.
See the list of available models for pricing.
Limitations
Native embeddings have a few limitations. Like our other limits, there is nothing here that we can't improve if prioritized. If your native embeddings use case is blocked by one of these limits, contact us.
-
Native embeddings always store the source document in turbopuffer, along with the embedding vector.
-
You're constrained by our rate limits. We work tirelessly with our partners to make sure turbopuffer can embed as fast as it can index, but there may be situations where we have to rate-limit and return 429s. We keep a close eye on our capacity and scale up within and across providers to make sure you can keep puffin'.
-
Setting
embedon an attribute does not yet automatically re-embed it with the specified model. Only subsequent writes are affected, so if you decide to change your embedding model you have to re-ingest the data in your namespaces. -
Multi-modal support is a work-in-progress. There is no dedicated type for image or video data, so to use multi-modal embeddings your source data must be stored as a base64 encoded
stringattribute. -
We don't yet support contextual models that chunk their inputs and produce a variable number of vectors. We want to get the API right before we make them available.
Models
The following models are currently available as managed embedding models. If you see a model you'd like to use that isn't listed here, please get in touch.
Prices are subject to change while embedding is still in private beta. A model listed here without a price is still available, but pricing is TBD. We'll always try to work with our partners to price match your existing embedding provider.
Recommendations
Picking an embedding model can deeply impact the quality, cost, and latency of your retrieval pipeline. Unfortunately, it's both a difficult decision and one that's hard to change (though we're working on making it easy). The quality of an embedding model is highly dependent on exactly what you're searching over. The best way to choose an embedding model is to run an eval that measures how an embedding model affects your application. If you can't do that, the RTEB scores in the table below are an ok guide.
When picking a model, we recommend thinking about the quality, cost, and latency tradeoffs in your application. It may be tempting to always pick the "best" model, but if you need interactive search your users may be more sensitive to latency than they are to slightly worse recall. Conversely, if you're powering a search agent it may be better to spend more up-front on an embedding model to reduce the number of tokens the agent needs to answer questions.
The models below are grouped into S, M, and L categories that roughly
represent the number of parameters in a model and how long it takes to embed a
document. S models prioritize latency over quality and are ideal for
interactive applications that prioritize speed. L models maximize quality at
the expense of latency, and are ideal for applications where you're happy to
wait for high quality results. M models are somewhere in between.
Model list
Click on a model for more detailed information.
-
Context length is the maximum number of input tokens a model will consider in an individual embedding input.
-
The dimension marked
(default)is the model's default output dimension. -
The listed RTEB score is the overall score.
If you're interested in using a model not available in the list below contact us.
| Details | ||||
|---|---|---|---|---|
baai/bge-m3 | TBD | S | 56.39 | |
cohere/embed-english-v3.0 | $0.10 | M | — | |
cohere/embed-v4.0 | $0.12 | L | 72.99 | |
google/gemini-embedding-001 | $0.15 | L | 75.85 | |
google/gemini-embedding-2 | $0.20 | L | 68.40 | |
qwen/qwen3-embedding-0p6b | TBD | S | — | |
qwen/qwen3-embedding-4b | TBD | M | — | |
qwen/qwen3-embedding-8b | TBD | L | 72.43 | |
voyage/voyage-4 | $0.06 | M | 77.94 | |
voyage/voyage-4-large | $0.12 | L | 79.70 | |
voyage/voyage-4-lite | $0.02 | S | 75.07 | |
voyage/voyage-4-nano | TBD | S | — | |
voyage/voyage-code-3 | $0.18 | L | 23.15 | |
zeroentropy/zembed-1 | TBD | M | — |
Examples
Creating a new namespace
Create a new namespace with native embeddings on a text attribute.
import { Turbopuffer } from "@turbopuffer/turbopuffer";
const tpuf = new Turbopuffer({
region: "gcp-us-central1", // pick a region: https://turbopuffer.com/docs/regions
});
// create a new namespace with native embeddings
const ns = tpuf.namespace(`embedding-new-namespace-ts`);
await ns.write({
upsert_rows: [
{
id: 1,
text: "A cat sleeping on a windowsill",
category: "animal",
},
{
id: 2,
text: "A playful kitten chasing a toy",
category: "animal",
},
],
distance_metric: "cosine_distance",
schema: {
text: {
type: "string",
// use native embeddings to set an embedding model and the size
// of the embedding vector
embed: {
model: "example/random",
dims: 512,
}
},
},
});
// future writes don't need to specify the schema or the `embed_text` attribute.
// the text attribute is turned into a vector on every write.
await ns.write({
upsert_rows: [
{
id: 3,
text: "An airplane flying through clouds",
category: "vehicle",
},
{
id: 4,
text: "A shiny red sports car",
category: "vehicle",
},
]
})
// use the Embed function with ANN or kNN to turn text input into a vector
await ns.query({
include_attributes: ["id", "text", "embed_text"],
rank_by: ["text", "ANN", ["Embed", "something high energy"]],
limit: 10,
});Query-only native embeddings
If you've already invested in embedding a large batch of documents, you can ingest them into turbopuffer and use native embeddings to query them.
import { Turbopuffer } from "@turbopuffer/turbopuffer";
const tpuf = new Turbopuffer({
region: "gcp-us-central1", // pick a region: https://turbopuffer.com/docs/regions
});
function embed(_text: string): number[] {
return [Math.random(), Math.random()];
}
// creating a new namespace that uses your own embedding model to embed
// documents, and write a few documents into the namespace.
const ns = tpuf.namespace(`embedding-query-only-ts`);
await ns.write({
upsert_rows: [
{
id: 1,
vector: embed("walrus narwhal"),
public: true,
text: "walrus narwhal",
},
{
id: 2,
vector: embed("pufferfish clownfish swordfish"),
public: false,
text: "pufferfish clownfish swordfish",
},
],
distance_metric: "cosine_distance",
schema: {
text: { type: "string", full_text_search: true, regex: true },
},
});
// use the Embed function with an explicit model to let native embeddings handle
// the query embeddings. this should match the model you're using to embed
// documents or you might get some strange results.
await ns.query({
include_attributes: ["id", "text", "vector"],
rank_by: ["vector", "ANN", ["Embed", "something high energy", {model: "example/random"}]],
limit: 10,
});Migrating to native embeddings
To migrate an existing namespace to native embeddings, first switch your
application's reads to use native embeddings on
query and then switch writes by
updating the embed attribute on the
attribute to embed. Future writes to that namespace must not provide that
vector, and future ANN queries can use the Embed function to rank by the
source field directly.
Embedding an existing attribute into an existing vector does check that the embedding model supports both the shape and datatype of the existing vector, but does not check that the vectors semantically make sense and does not re-embed the namespace. Before enabling native embedding on a namespace, validate that the model already in use is compatible with the native embedding model.
import { Turbopuffer } from "@turbopuffer/turbopuffer";
const tpuf = new Turbopuffer({
region: "gcp-us-central1", // pick a region: https://turbopuffer.com/docs/regions
});
function random_vector(_text: string): number[] {
return [Math.random(), Math.random()];
}
// create a new namespace without native embeddings
const ns = tpuf.namespace(`embedding-enable-ts`);
await ns.write({
upsert_rows: [
{
id: 1,
vector: random_vector("walrus narwhal"),
text: "walrus narwhal",
},
{
id: 2,
vector: random_vector("pufferfish clownfish swordfish"),
text: "pufferfish clownfish swordfish",
},
],
distance_metric: "cosine_distance",
schema: {
text: { type: "string", full_text_search: true, regex: true },
},
});
// switch your queries to use query-time embedding
await ns.query({
include_attributes: ["id", "text", "vector"],
rank_by: ["vector", "ANN", ["Embed", "a pointy fish", {model: "example/random"}]],
limit: 10,
});
// update your schema in-place to enable native embeddings
await ns.write({
schema: {
text: {
type: "string",
embed: {
model: "example/random",
attribute: "vector",
},
},
},
});
// queries no longer need to specify a model
await ns.query({
include_attributes: ["id", "text", "vector"],
rank_by: ["text", "ANN", ["Embed", "lots and lots of blubber"]],
limit: 10,
});
// and writes no longer need to include a vector
await ns.write({
upsert_rows: [
{
id: 3,
text: "zebra horse antelope",
},
]
});Disabling native embeddings
To disable embedding, set embed to null on an attribute. After the write
succeeds, any future writes will require passing an explicit vector and any
future ANN queries will require passing a vector to rank_by. Any embedded
columns, including automatically generated embedding columns, are untouched
and can be queried as normal.
import { Turbopuffer } from "@turbopuffer/turbopuffer";
const tpuf = new Turbopuffer({
region: "gcp-us-central1", // pick a region: https://turbopuffer.com/docs/regions
});
function random_vector(_text: string, n: number = 8): number[] {
return Array.from({ length: n }, () => Math.random());
}
// create a new namespace with native embeddings
const ns = tpuf.namespace(`embedding-disable-ts`);
await ns.write({
upsert_rows: [
{
id: 1,
text: "A cat sleeping on a windowsill",
category: "animal",
},
{
id: 2,
text: "A playful kitten chasing a toy",
category: "animal",
},
],
distance_metric: "cosine_distance",
schema: {
text: {
type: "string",
embed: {
model: "example/random",
dims: 8,
},
},
},
});
// disable native embeddings
await ns.write({
schema: {
text: {
type: "string",
embed: null,
},
},
});
// any future write to this namespace requires writing the embed_text vector
await ns.write({
upsert_rows: [
{
id: 1,
embed_text: random_vector("zebra horse antelope"),
text: "zebra horse antelope",
},
],
});Security
We work with external partners to provide access to a wide range of embedding models. When you use automatic embedding, your data is sent from our cloud account to one of our embedding partners. The exact list of partners is model dependent, and listed on our subprocessors page.