Declare a schema and upsert documents¶
A document index is an index created with a schema that names its searchable fields.
You write and read it through index.documents, and each record is a JSON document: a
reserved _id plus your own fields. This page covers choosing fields, creating the
index, and loading data.
If your index was created another way, it uses a different interface: top-level
dimension=/metric= indexes take upsert() (see
Upserting and querying vectors), and indexes from
create_for_model() take the legacy Records API (see
Legacy integrated indexes).
Choose your fields¶
Declare only the fields you search. Each kind of field is searched with its own
score_by clause:
Field |
Your documents carry |
Search it with |
|---|---|---|
|
a list of floats from your embedding model |
|
|
|
|
|
text |
|
|
text; Pinecone embeds it with a hosted dense model |
|
|
text; Pinecone embeds it with a hosted sparse model |
An index holds at most one dense field (a dense_vector, or a string with embed) and
at most one sparse field (a sparse_vector, or a string with sparse_embed). Values
you only filter on, such as a category or a year, stay out of the schema: put them on
the documents and they are indexed for filtering when first written.
The schema cannot change after the index is created, so declare every searched field up front.
Create the index¶
SchemaBuilder lays out the field types and their
options. This index stores your own embeddings and also supports keyword search on the
title:
from pinecone import Pinecone, SchemaBuilder
pc = Pinecone(api_key="your-api-key")
schema = (
SchemaBuilder()
.add_dense_vector_field("embedding", dimension=1536, metric="cosine")
.add_string_field("title", full_text_search={"language": "en", "stemming": True})
.build()
)
pc.indexes.create(name="article-search", schema=schema)
dimension and metric must match the embedding model you use. create blocks until
the index is ready; pass timeout=-1 to return immediately. Without deployment=, the
index is serverless on AWS us-east-1; see
Working with serverless indexes to choose another cloud or
region.
To have Pinecone embed text for you, give a string field embed, sparse_embed, or
both. True takes the default model:
schema = SchemaBuilder().add_string_field("body", embed=True, sparse_embed=True).build()
pc.indexes.create(name="support-articles", schema=schema)
The text of an embed field is stored and full-text searchable unless you pass
store_text=False. See
Integrated embedding for choosing a model
and what store_text=False gives up.
build() returns a plain {"fields": {...}} dict, and writing that dict yourself is
equally supported. The builder does not decide what the server accepts: an invalid
combination is sent as written, and create raises
ApiError with the server’s message.
Upsert documents¶
upsert() writes a list of documents to a
namespace. A document whose _id already exists is replaced.
index = pc.index("article-search")
response = index.documents.upsert(
namespace="articles-en",
documents=[
{
"_id": "article-101",
"embedding": [0.012, -0.087, 0.153], # 1536 floats in practice
"title": "How Roman aqueducts carried water",
"category": "history",
"year": 2024,
},
{
"_id": "article-102",
"embedding": [0.045, 0.021, -0.064],
"title": "Gravity-fed irrigation today",
"category": "engineering",
"year": 2023,
},
],
)
print(response.upserted_count)
Each document must carry every dense_vector field and the text of every embed field.
Sparse fields, full-text-only fields, and metadata such as category are optional. The
server rejects a document that is missing a required field with a 400 naming the
document and the field.
For the support-articles index above, a document is just its _id, its text, and any
metadata: {"_id": "article-101", "body": "Roman aqueducts carried water by gravity"}.
Documents can also be built as DocumentRecord
objects, which check the _id where you construct them.
Load large datasets¶
upsert sends one request, and the server limits a request by document count and by
size. For more documents than that, or documents of unknown size, use
batch_upsert(). It splits the list into
batches, sends them in parallel, and halves any batch the server rejects as too large.
Here documents is a list of document dicts like the ones above:
result = index.documents.batch_upsert(namespace="articles-en", documents=documents)
for error in result.errors:
print(error.batch_index, error.retryable, error.error_message)
A failed batch is recorded in the returned BatchResult
instead of raised, so one bad batch does not stop the rest. Resend result.failed_items
after fixing whatever result.errors reports. On an index with an embed or
sparse_embed field, the server accepts fewer documents per request; the default
batch_size fits, so raise it only on other indexes. See
How bulk ingest behaves for tuning batch_size and
max_concurrency.
Wait for documents to be visible¶
Upserts apply asynchronously: upsert returns once the server accepts the documents, and
a search or fetch made right afterward may not see them yet. Code that reads its own
writes, such as a test or a demo script, should poll until the documents appear:
import time
ids = ["article-101", "article-102"]
while len(index.documents.fetch(namespace="articles-en", ids=ids).documents) < len(ids):
time.sleep(1)
Async client¶
AsyncDocuments on
AsyncIndex has the same methods with the same
arguments; await each call:
from pinecone import AsyncPinecone
async with AsyncPinecone(api_key="your-api-key") as pc:
index = await pc.index(name="article-search")
async with index:
await index.documents.upsert(namespace="articles-en", documents=documents)