Quickstart Guide
Walk a tiny namespace through the core loop: connect, write rows with vectors and attributes, query them, simple aggregations, then layer on conditional writes and branching.
If you are an agent, you may wish to read the full documentation in Markdown.
Connect
-
Install an SDK:
# https://github.com/turbopuffer/turbopuffer-python pip install turbopuffer -
Create an API key from the Dashboard. The snippets default to
gcp-us-central1; change it to your preferred region if needed. Not every model runs in every region, so if you change either, check the model list. -
The snippets use native embeddings, so turbopuffer embeds your text on writes and queries and you don't integrate an embedding provider at all. To call a provider from your own code instead, pick one from the dropdown in the code sample below.
import os
import uuid
import turbopuffer
tpuf = turbopuffer.Turbopuffer(
api_key=os.getenv("TURBOPUFFER_API_KEY"), # created here: https://turbopuffer.com/dashboard
region="gcp-us-central1", # choose best region: https://turbopuffer.com/docs/regions
)
namespace = os.getenv("TURBOPUFFER_NAMESPACE", f"quickstart-{uuid.uuid4().hex[:8]}")
ns = tpuf.namespace(namespace)Write
Upsert documents with typed attributes and
full-text search on text and category (with
regex on text). Setting embed on text turns it
into a vector on every write, so no vectors go over the wire.
ns.write(
upsert_rows=[
{
'id': 1,
'category': ["mammal"],
'public': True,
'text': "walrus narwhal",
},
{
'id': 2,
'category': ["fish"],
'public': False,
'text': "pufferfish clownfish swordfish",
},
],
distance_metric='cosine_distance',
schema={
"text": {
"type": "string",
"full_text_search": True,
"regex": True,
"embed": {"model": "nvidia/nemotron-3-embed-8b", "dims": 1024},
},
"category": {
"type": "[]string",
"full_text_search": True,
},
}
)Search
Find documents by vector similarity with filters, where
Embed embeds the query text with the model configured on text. Or search
by full-text search with a
boosted category field, or by
regex (\w+fish matches "pufferfish",
"swordfish", "clownfish"). To combine vector and FTS concurrently, see
hybrid search.
# Vector search with a filter
print(ns.query(
rank_by=("text", "ANN", ("Embed", "arctic sea mammal")),
limit=10,
filters=("public", "Eq", True),
))
# Full-text search with boosted category field
print(ns.query(
limit=10,
filters=("public", "Eq", True),
rank_by=("Sum", [
("Product", 2, ("category", "BM25", "mammal")),
("text", "BM25", "quick walrus"),
]),
))
# Regex filter — matches "pufferfish", "swordfish", "clownfish"
print(ns.query(
limit=10,
filters=("text", "Regex", "\\w+fish"),
))Aggregate
Count documents without returning rows, and use
grouped aggregations to split the counts by attribute.
Stay in the same namespace and count rows per category.
grouped = ns.query(
aggregate_by={"count_by_category": ("Count",)},
group_by=["category"],
)
print(grouped.aggregation_groups)
# [Row(category=['fish'], count_by_category=1), Row(category=['mammal'], count_by_category=1)]Full runnable example
Prefer one copy-paste program for the core loop? This version covers connect, write, search, and aggregate in one file. Then continue below with the smaller conditional-write and branching snippets.
# $ pip install turbopuffer
# Sample Python notebook:
# https://colab.research.google.com/drive/17i4sfFTeJQkINCxjBaOGOZeENZr4ZaTE
import os
import uuid
import turbopuffer
tpuf = turbopuffer.Turbopuffer(
api_key=os.getenv("TURBOPUFFER_API_KEY"), # created here: https://turbopuffer.com/dashboard
region="gcp-us-central1", # choose best region: https://turbopuffer.com/docs/regions
)
namespace = os.getenv("TURBOPUFFER_NAMESPACE", f"quickstart-{uuid.uuid4().hex[:8]}")
ns = tpuf.namespace(namespace)
# Upsert documents with attributes
ns.write(
upsert_rows=[
{
'id': 1,
'category': ["mammal"],
'public': True,
'text': "walrus narwhal",
},
{
'id': 2,
'category': ["fish"],
'public': False,
'text': "pufferfish clownfish swordfish",
},
],
distance_metric='cosine_distance',
schema={
"text": {
# Configure FTS/BM25. Other attributes get inferred types.
"type": "string",
# More schema & FTS options:
# https://turbopuffer.com/docs/write#schema
"full_text_search": True,
"regex": True,
# Native embeddings. Check the model is available in your region:
# https://turbopuffer.com/docs/embedding#models
"embed": {"model": "nvidia/nemotron-3-embed-8b", "dims": 1024},
},
"category": {
"type": "[]string",
"full_text_search": True,
},
}
)
# Query nearest neighbors with a filter
print(ns.query(
rank_by=("text", "ANN", ("Embed", "arctic sea mammal")),
limit=10,
filters=("public", "Eq", True),
))
# [Row(id=1, $dist=...)]
# Full-text search on an attribute
# To combine FTS and vector search concurrently, see:
# https://turbopuffer.com/docs/hybrid-search
print(ns.query(
limit=10,
filters=("public", "Eq", True),
rank_by=("Sum", [
("Product", 2, ("category", "BM25", "mammal")),
("text", "BM25", "quick walrus"),
]),
))
# [Row(id=1, vector=None, $dist=0.7549128)]
# Regex filter — matches "pufferfish", "swordfish", "clownfish"
print(ns.query(
limit=10,
filters=("text", "Regex", "\\w+fish"),
))
# Count documents grouped by category
grouped_result = ns.query(
aggregate_by={"count_by_category": ("Count",)},
group_by=["category"],
)
print(grouped_result.aggregation_groups)
# [Row(category=['fish'], count_by_category=1), Row(category=['mammal'], count_by_category=1)]Conditional writes
Only update a document when a condition is met -- for example,
keep only the newest timestamped write.
Continue from the same namespace and only apply the write when the new
updated_at is newer than the stored one, or when the row has no timestamp yet.
# Only update if this write has a newer timestamp
result = ns.write(
upsert_rows=[{
'id': 1,
'text': "updated walrus",
'category': ["mammal"],
'updated_at': "2024-04-16T09:27:32Z",
}],
upsert_condition=(
'Or', [
('updated_at', 'Lt', {'$ref_new': 'updated_at'}),
('updated_at', 'Eq', None),
]
),
distance_metric='cosine_distance',
)
print(result.rows_affected) # 1Branching
Instantly clone a namespace with copy-on-write. Use it to spin up isolated test environments, keep lightweight versioned copies, or take snapshots before risky changes. Constant-time regardless of size, and fully independent after creation. Finally, branch the same namespace into a fresh copy and query it independently.
branch_namespace = f"{namespace}-branch"
branch = tpuf.namespace(branch_namespace)
branch.write(branch_from_namespace=namespace)
# Query the branch independently
print(branch.query(
rank_by=("text", "ANN", ("Embed", "sea creature")),
limit=5,
))What's next
- Write docs -- schema, patches, deletes, delete-by-filter
- Query docs -- kNN, hybrid search, ordering, grouped aggregations
- Concepts -- namespaces, attributes, distance metrics
- Architecture -- how object storage makes this work