Getting started: upsert your first vectors
ManyVector stores all vector data in your own object store — your S3, GCS, R2, or MinIO bucket. This guide walks you through the complete setup: creating a namespace, connecting your bucket, and upserting your first vectors.
Prerequisites
- A ManyVector account (sign up at manyvector.com/demo)
- An AWS S3, GCS, Cloudflare R2, or MinIO bucket you control
- Python 3.9+ and pip
Step 1: Install the SDK
pip install manyvector
Verify the installation:
python -c "import manyvector; print(manyvector.__version__)"
Step 2: Connect your object store
ManyVector never stores your data — it reads and writes only to your bucket. You provide credentials scoped to that bucket.
For S3, create an IAM policy with minimal permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::my-vector-bucket", "arn:aws:s3:::my-vector-bucket/*"]
}
]
}
Step 3: Create a namespace
A namespace is the top-level collection. Each namespace maps to a prefix in your bucket.
from manyvector import ManyVector
client = ManyVector(
api_key="mv-...",
backend={
"provider": "s3",
"bucket": "my-vector-bucket",
"region": "us-east-1",
"access_key_id": "AKIA...",
"secret_access_key": "...",
}
)
# Create a namespace for your documents
ns = client.create_namespace(
name="my-docs",
dimensions=1536, # must match your embedding model
metric="cosine",
)
print(f"Created namespace: {ns.name}")
The dimensions parameter must match the output dimension of your embedding model. For text-embedding-3-small (OpenAI) this is 1536. For all-MiniLM-L6-v2 (sentence-transformers) this is 384.
Step 4: Generate embeddings
You can use any embedding model. Here’s an example using the OpenAI SDK:
import openai
openai_client = openai.OpenAI(api_key="sk-...")
def embed(texts: list[str]) -> list[list[float]]:
response = openai_client.embeddings.create(
input=texts,
model="text-embedding-3-small",
)
return [item.embedding for item in response.data]
Step 5: Upsert vectors
Each vector record has an id, a vector, and optional attributes (stored alongside the vector, searchable via BM25 or filterable):
documents = [
{"id": "doc-1", "text": "ManyVector stores vectors in object storage."},
{"id": "doc-2", "text": "HNSW enables approximate nearest-neighbour search."},
{"id": "doc-3", "text": "BM25 provides full-text search ranking."},
]
# Embed all documents in a batch
embeddings = embed([d["text"] for d in documents])
# Upsert with attributes
ns = client.namespace("my-docs")
ns.upsert([
{
"id": doc["id"],
"vector": emb,
"text": doc["text"],
"source": "guide",
}
for doc, emb in zip(documents, embeddings)
])
print(f"Upserted {len(documents)} vectors")
Step 6: Run your first query
query = "How does vector search work?"
query_embedding = embed([query])[0]
results = ns.query(
vector=query_embedding,
top_k=3,
)
for r in results:
print(f" {r.id}: score={r.score:.4f} | {r.attributes['text'][:60]}...")
What just happened?
When you upserted, ManyVector wrote:
- The raw vector data as objects in your bucket
- An HNSW graph index (also stored in your bucket)
- An inverted index for BM25 full-text search
When you queried, ManyVector:
- Loaded the HNSW graph from your bucket (or used the cached version)
- Traversed the graph to find approximate nearest neighbours
- Returned results ranked by cosine similarity
Next steps
- Add metadata filters to your queries
- Try hybrid search for higher-quality RAG retrieval
- Fork a namespace to test new embedding models safely
Migrating from Pinecone to ManyVector
Step-by-step migration guide: export vectors from Pinecone, import into ManyVector, verify recall parity, and cut over traffic without downtime.
Read →Hybrid search query patterns
Practical patterns for combining vector and full-text search with ManyVector: alpha tuning, pre-filtering, re-ranking, and corpus-specific strategies.
Read →