Connect
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key=API_KEY) # or PINECONE_API_KEY env var
pc.list_indexes().names()
desc = pc.describe_index("kb-prod") # dimension, metric, host, status
index = pc.Index(host=desc.host) # cache the host; avoid a lookup per request
Create a serverless index:
pc.create_index(
name="kb-prod",
dimension=1536,
metric="cosine", # cosine | dotproduct | euclidean
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
deletion_protection="enabled",
)
Dimension and metric can't be changed later. A new embedding model with a different dimension needs a new index.
Upsert
index.upsert(
vectors=[
{"id": "doc42#0", "values": emb0, "metadata": {"doc_id": "doc42", "source": "confluence",
"year": 2026, "tags": ["vpn", "network"]}},
{"id": "doc42#1", "values": emb1, "metadata": {"doc_id": "doc42", "source": "confluence",
"year": 2026, "tags": ["vpn"]}},
],
namespace="kb-prod",
)
Same ID = overwrite (values and full metadata replaced).
Query
res = index.query(
vector=qvec,
top_k=10,
namespace="kb-prod",
filter={"source": {"$eq": "confluence"}, "year": {"$gte": 2025}},
include_metadata=True,
include_values=False, # values make responses much larger
)
for m in res.matches:
print(m.id, round(m.score, 4), m.metadata.get("doc_id"))
Query by an existing record's vector: index.query(id="doc42#0", top_k=5, namespace="kb-prod").
Filter operators
| Operator | Example | Matches |
|---|---|---|
$eq | {"source": {"$eq": "jira"}} | Equal (also {"source": "jira"}) |
$ne | {"status": {"$ne": "archived"}} | Not equal |
$gt, $gte, $lt, $lte | {"year": {"$gte": 2025}} | Numbers only |
$in | {"source": {"$in": ["jira", "confluence"]}} | Any of the values |
$nin | {"team": {"$nin": ["hr"]}} | None of the values |
$exists | {"reviewed": {"$exists": True}} | Field present |
$and | {"$and": [{"year": {"$gte": 2025}}, {"source": "jira"}]} | Both |
$or | {"$or": [{"source": "jira"}, {"priority": {"$lte": 2}}]} | Either |
Several fields at the top level are an implicit $and.
List fields: for a metadata value that's a list of strings ("tags": ["vpn","network"]), {"tags": {"$in": ["vpn"]}} matches if any element matches, and {"tags": "vpn"} matches records whose list contains vpn.
Metadata rules that cause filter bugs
| Rule | Consequence |
|---|---|
| Values: string, number, boolean, list of strings | Nested objects aren't filterable; flatten them (owner_team, not owner.team) |
| Numbers are compared as numbers | Store dates as epoch seconds or YYYYMMDD integers, not ISO strings, if you need ranges |
null values aren't supported | Omit the key instead; filter with $exists |
| 40 KB of metadata per record | Large chunk text in metadata eats the budget; store a pointer if needed |
| Case-sensitive strings | "Jira" ≠ "jira"; normalise at ingestion |
| Range operators on strings don't work | Use numeric fields |
Fetch, update, list
index.fetch(ids=["doc42#0", "doc42#1"], namespace="kb-prod")
# Partial metadata update: merges keys, leaves the vector untouched
index.update(id="doc42#0", set_metadata={"reviewed": True}, namespace="kb-prod")
# Replace the vector only
index.update(id="doc42#0", values=new_emb, namespace="kb-prod")
# List IDs by prefix (serverless) - useful with doc_id#chunk ID schemes
for ids in index.list(prefix="doc42#", namespace="kb-prod"):
print(ids)
The doc_id#chunk ID convention makes "all chunks of a document" a cheap prefix listing.
Delete
index.delete(ids=["doc42#0", "doc42#1"], namespace="kb-prod") # up to 1,000 IDs per call
index.delete(filter={"doc_id": {"$eq": "doc42"}}, namespace="kb-prod") # by metadata
index.delete(delete_all=True, namespace="kb-prod") # empty the namespace
Delete-by-filter has its own, lower rate limits than delete-by-ID. For bulk cleanup, list IDs by prefix and delete by ID in batches.
Caution
delete_all=Truewithout the rightnamespaceempties the default namespace. Wrap deletes in a helper that refuses an empty namespace.
Stats and health
stats = index.describe_index_stats()
stats.dimension, stats.total_vector_count
stats.namespaces["kb-prod"].vector_count
Counts are eventually consistent; they can trail just-written data.
Common errors
| Error | Meaning |
|---|---|
Vector dimension 3072 does not match the dimension of the index 1536 | Wrong embedding model or index |
400 ... metadata size exceeds the limit | Metadata over 40 KB for a record |
404 on index | Wrong host, or index name in another project |
429 | Rate limit; back off and batch |
401/403 | Wrong key or key from another project |
Prevent mistakes next time
- Define a metadata schema (field, type, allowed values) and validate before upsert.
- Store dates as integers for ranges.
- Use
doc_id#chunk_noIDs. - Keep
include_values=Falsein production queries.