Four verbs cover almost everything. Create an index with a fixed dimension and distance metric, write vectors with upsert, read with query, remove with delete. Metadata rides along with each vector for filtering.
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key="pc-...")
# 1) create_index: name + dimension + metric are locked at creation
pc.create_index(
name="advisor-docs",
dimension=1536, # must match your embedding model
metric="cosine", # cosine | dotproduct | euclidean
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)
index = pc.Index("advisor-docs")
# 2) upsert: id + values + metadata. Insert-or-update by id.
index.upsert(vectors=[
{"id": "doc-101", "values": emb1,
"metadata": {"doc_type": "policy", "year": 2025, "tenant": "pgim"}},
{"id": "doc-102", "values": emb2,
"metadata": {"doc_type": "faq", "year": 2024, "tenant": "pgim"}},
], namespace="pgim")
# 3) query: nearest top_k, optionally filtered by metadata
res = index.query(
vector=query_emb,
top_k=5,
include_metadata=True, # or you get ids + scores only
filter={"doc_type": {"$eq": "policy"}, "year": {"$gte": 2024}},
namespace="pgim",
)
for m in res["matches"]:
print(m["id"], m["score"], m["metadata"])
# 4) delete: by id, by filter, or delete_all in a namespace
index.delete(ids=["doc-102"], namespace="pgim")
✅ Where it slots into your project In rag-docs-chatbot your retrieval step becomes one index.query(...) call instead of a hand-rolled similarity loop. The embedding step stays yours, Pinecone only replaces the "find nearest" part.
⚠ Gotcha upsert is insert-or-update by id. Re-upserting the same id overwrites values and metadata. Great for re-embedding, but a duplicate id silently clobbers the old vector. Generate stable ids from content, not from a counter that resets.
Formal terms Upsert: idempotent write keyed by id. top_k: number of neighbors returned. Filter DSL: Mongo-style operators $eq $ne $gt $gte $in $nin. Score: the similarity value under the index metric.