Pass the file path to vp.vectors.upsert() — the file extension selects the parser, and one call uploads, processes, and blocks until the data is queryable. Parquet, CSV, and JSONL files carry an id column, a vector column, and any number of metadata columns; the raw vector formats (.fvecs, .npy, and friends) carry just vectors and get stable generated keys. There is no separate bulk-import API or staging step: the same upsert call handles a three-row file and a multi-gigabyte one.
Here's the whole path, writing a Parquet file from the 5,000-film sample corpus and uploading a slice of it:
from veep import VP, samples
df = samples.dataframe() # columns: id, title, year, genre, plot, vector
df.head(500).to_parquet("films.parquet")
vp = VP.from_creds()
vp.collections.create("films-from-parquet", tier="hot",
id_field="id", vector_field="vector")
result = vp.vectors.upsert("films-from-parquet", "films.parquet")
print(result)
UploadResult(status='created', collection='films-from-parquet', filename='films.parquet', size=1760550)
When upsert returns, the vectors are live — the very next query sees them:
qv = samples.encode("a small team plans an elaborate casino heist")
hit = vp.vectors.query("films-from-parquet", vector=qv, top_k=1)[0]
print(f"{hit.score:.4f} {hit.metadata['title']}")
0.4782 Ocean's Eleven
JSONL is the same call with a different extension — one JSON object per line, vector as a plain array:
df.head(500).to_json("films.jsonl", orient="records", lines=True)
vp.collections.create("films-from-jsonl", tier="hot",
id_field="id", vector_field="vector")
vp.vectors.upsert("films-from-jsonl", "films.jsonl")
For CSV, encode each vector as a bracketed string in one column — "[0.017,-0.038,...]" — which is exactly what pgvector's \copy emits, so a Postgres export ingests directly (worked example in How do I migrate from pgvector?).
Every supported extension
.parquet, .csv, .jsonl/.json, .arrow/.feather/.ipc, .bvecs/.fvecs/.ivecs, .fbin/.u8bin/.i8bin, .npy/.npz, .h5/.hdf5/.hdf, and .safetensors.
The vectors-only formats (.fvecs, .npy, benchmark dumps, tensor files) have no id column, so each row gets the key {filename}:{row_index} — stable across re-uploads of the same file, usable in filters and fetches, and preserved through export, so nothing about starting from a raw format locks you out of ids later.
No files at all? Two more modes of the same call: upsert(collection, vectors=[{"id": ..., "vector": [...], "metadata": {...}}]) for a handful of rows (covered in Inline upserts), and upsert(collection, dataframe=df) to skip the file step entirely when your data is already in pandas.
vp.collections.delete("films-from-parquet")
vp.collections.delete("films-from-jsonl")
