You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: skills/pinecone-full-text-search/references/ingestion.md
+78-37Lines changed: 78 additions & 37 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,11 @@
1
1
# Ingestion
2
2
3
-
Writing documents into a Pinecone preview document index uses two methods. Pick based on volume, then handle the *async indexing* gotcha on the other side.
3
+
Writing documents into a Pinecone document index uses two methods. Pick based on volume, then handle the *async indexing* gotcha on the other side.
- You're writing a single document (e.g. a sentinel doc to verify end-to-end before a bulk load).
29
-
- You're "patching" a doc after a correction. *Note*: `2026-01.alpha` has **no per-field merge** — every upsert replaces the entire document on conflicting `_id`. To update a single field, fetch the doc, modify in client code, and upsert the full doc back under the same `_id`.
30
29
- You're streaming writes from user actions and each request fits in a single batch.
30
+
- You want to fully replace a document by `_id` (upsert always replaces the whole document on conflict — for a true per-field patch, use `documents.update` below instead).
31
31
32
-
Each document is a dict keyed by field name. `_id` is required and must be a non-empty unique string within the namespace. Values must match the declared schema types (FTS strings → `str`, filterable `float` → `int|float`, dense vectors → `list[float]`, sparse → `{"indices": [...], "values": [...]}`). Field names that start with `_` or `$` are rejected; field names are limited to 64 bytes.
32
+
Each document is a dict keyed by field name (or a `DocumentRecord`/`UpdateDocumentRecord` instance — both accept a plain dict positionally or fields as keyword arguments). `_id` is required and must be a non-empty unique string within the namespace. Values must match the declared schema types for the few fields actually declared in the schema (FTS strings → `str`, dense vectors → `list[float]`, sparse → `{"indices": [...], "values": [...]}`); every other field on the document — strings, numbers, booleans, string lists — is auto-indexed metadata with nothing declared for it. Field names that start with `_` or `$` are rejected; field names are limited to 64 bytes.
33
33
34
34
The endpoint returns `202 Accepted` (async) and the body's `upserted_count` is the number of items accepted, not the number that have finished indexing.
35
35
@@ -39,8 +39,8 @@ The endpoint returns `202 Accepted` (async) and the body's `upserted_count` is t
The SDK splits `documents` into `batch_size`-sized chunks and uploads them over `max_workers` parallel HTTP connections. `show_progress=True` prints a tqdm-style bar.
57
+
The SDK splits `documents` into `batch_size`-sized chunks and uploads them over `max_concurrency` parallel HTTP connections. `show_progress=True` prints a tqdm-style bar. `max_concurrency` must be a plain `int` (default `4`) — it no longer accepts `None`.
58
58
59
-
### Tuning `batch_size` and `max_workers`
59
+
### Tuning `batch_size` and `max_concurrency`
60
60
61
-
-**`batch_size=50`** is the sweet spot — comfortably below the per-request cap and small enough that transient failures cost less to redo.
62
-
-**`max_workers=2`**is a safe default. Bump to `4` for large (thousands-of-docs) loads where you're not simultaneously embedding. Ramp cautiously above 4 — you'll hit Pinecone or upstream embedding-provider rate limits first.
63
-
- If you're embedding on the fly (computing vectors inside the upsert loop), keep `max_workers` low so embedding latency dominates rather than index write latency.
61
+
-**`batch_size=50`**(the SDK's own default) is the sweet spot — comfortably below the per-request cap and small enough that transient failures cost less to redo.
62
+
-**`max_concurrency=4`**(the SDK's own default) is a safe default for large (thousands-of-docs) loads where you're not simultaneously embedding. Ramp cautiously above 4 — you'll hit Pinecone or upstream embedding-provider rate limits first.
63
+
- If you're embedding on the fly (computing vectors inside the upsert loop), keep `max_concurrency` low so embedding latency dominates rather than index write latency.
64
64
65
65
### Document and request size caps
66
66
67
-
**Hard limits in `2026-01.alpha`:**
67
+
**Hard limits in `2026-07`:**
68
68
69
69
-**Per document**: max **2 MB** (serialized JSON, all stored fields combined).
70
70
-**Per `full_text_search` string field**: max **100 KB** AND max **10,000 tokens**. Tokens longer than 256 bytes are silently truncated by the analyzer.
@@ -76,7 +76,7 @@ If any one of these is exceeded, the batch fails as a whole. The most common lim
76
76
77
77
### Dense-vector payload size
78
78
79
-
A high-dimensional dense field can silently turn a 50-doc batch into a 5–10 MB request, which the preview backend will reject wholesale. If every batch fails and the error message is opaque, the first thing to try is dropping the embedding dimension before debugging schema:
79
+
A high-dimensional dense field can silently turn a 50-doc batch into a 5–10 MB request, which the backend will reject wholesale. If every batch fails and the error message is opaque, the first thing to try is dropping the embedding dimension before debugging schema:
80
80
81
81
-**Gemini**: pass `config=types.EmbedContentConfig(output_dimensionality=768)`. The model uses Matryoshka representations, so smaller dimensions are valid truncations of the native output. 768 is usually a 4× payload reduction vs. the native 3072 and costs very little quality.
82
82
-**OpenAI `text-embedding-3-*`**: pass `dimensions=768` (or similar) to `embeddings.create`.
@@ -86,6 +86,8 @@ A high-dimensional dense field can silently turn a 50-doc batch into a 5–10 MB
86
86
87
87
After `batch_upsert` returns, **your documents are written but not yet searchable.** The server builds inverted indexes for FTS fields and ANN graphs for vector fields in the background. A search query issued immediately will return empty matches. Schemas with multiple indexed fields (e.g. text + dense + sparse) may take slightly longer.
88
88
89
+
This is distinct from — and in addition to — `pc.indexes.create(...)`'s own polling for the *index itself* to become ready (which it does by default before returning). Even a fully-ready index needs this separate poll after each ingest.
90
+
89
91
**Always poll with a deadline** before trusting the index:
90
92
91
93
```python
@@ -111,7 +113,7 @@ Pick a sentinel query likely to hit at least one document. For a typical corpus,
111
113
112
114
## Chunking oversized text
113
115
114
-
Per the public-preview docs (above), the per-FTS-field hard limits are 100 KB and 10,000 tokens. In practice, plan for the *token* limit kicking in first on natural prose (~5,000 English words at ~2 tokens each is the rough ceiling). Probe before ingesting at scale — chunk anything that approaches either bound, with safety margin.
116
+
Per the size caps above, the per-FTS-field hard limits are 100 KB and 10,000 tokens. In practice, plan for the *token* limit kicking in first on natural prose (~5,000 English words at ~2 tokens each is the rough ceiling). Probe before ingesting at scale — chunk anything that approaches either bound, with safety margin.
115
117
116
118
**Strategy: probe first, then chunk if needed.**
117
119
@@ -158,40 +160,80 @@ Conventions:
158
160
159
161
## Updating documents
160
162
161
-
There is **no per-field update or merge** in `2026-01.alpha`. `documents.upsert` always replaces the entire document for a given `_id`. To update one field:
163
+
`2026-07` has real per-field updates via `documents.update(...)` — this replaces the fetch → modify → re-upsert workaround from the old preview API. Two shapes:
164
+
165
+
**By `_id`, patching specific fields:**
162
166
163
167
```python
164
-
fetched =idx.documents.fetch(
168
+
idx.documents.update(
165
169
namespace=NAMESPACE,
166
-
ids=["doc-42"],
167
-
include_fields=["*"], # need the full doc to round-trip it
**By filter, patching every matching document at once:**
175
+
176
+
```python
177
+
resp = idx.documents.update(
178
+
namespace=NAMESPACE,
179
+
filter={"category": {"$eq": "fiction"}},
180
+
set_fields={"featured": True},
181
+
# remove_fields=["stale_field"], # drop a field entirely, by name
182
+
)
183
+
print(resp.matched_records) # point-in-time count, same semantics as documents.delete
173
184
```
174
185
175
-
If the document includes a dense vector, you re-upsert that vector verbatim. If it changes, embed the new content first.
186
+
`documents.update` accepts `documents=` (a list of per-record patches) *or*`filter=` + `set_fields=`/`remove_fields=` (a bulk patch by metadata match) — not both. `set_fields` merges into the existing document; fields you don't mention are left untouched. `remove_fields` deletes named fields outright. For a filtered update, `matched_records` on the response is a point-in-time count when the server accepted the request, the same caveat as `documents.delete`'s `matched_records` — the update itself applies asynchronously.
187
+
188
+
If you need to fully replace a document (including its vector fields) rather than patch it, `documents.upsert` with the complete document under the same `_id` is still the right tool — `upsert` replaces, `update` patches.
176
189
177
190
## Deletes
178
191
179
-
`documents.delete` accepts either `ids: [...]` (1–1000 items) or `delete_all: true`. There is **no delete-by-filter** — to delete documents matching a metadata expression, search first to collect IDs, then pass them in:
192
+
`documents.delete` accepts exactly one of `ids: [...]` (1–1000 items), `filter: {...}`, or `delete_all: true`:
`delete_all=True` wipes the entire namespace. Use carefully.
194
201
202
+
## Namespace management
203
+
204
+
Confirmed working against document-schema indexes in `2026-07` — this was **not** supported in the old preview API, which could write to a namespace but not list, describe, create, or delete one via the API.
205
+
206
+
```python
207
+
# Create explicitly (namespaces otherwise auto-create on first upsert — see below).
208
+
ns = idx.create_namespace(name="movies-en")
209
+
print(ns.name, ns.record_count, ns.size_bytes)
210
+
211
+
# List every namespace on the index in one call.
212
+
for page in idx.list_namespaces():
213
+
for ns in page.namespaces:
214
+
print(ns.name, ns.record_count)
215
+
216
+
# Describe one namespace. Prefer list_namespaces() over repeated describe_namespace()
217
+
# calls — describe_namespace is rate-limited per index; list_namespaces isn't.
218
+
ns = idx.describe_namespace(name="movies-en")
219
+
220
+
# Delete a namespace and everything in it.
221
+
idx.delete_namespace(name="movies-en")
222
+
```
223
+
224
+
`create_namespace`'s optional `schema` parameter (`{"fields": {"<field>": {"filterable": True}}}`) controls *which metadata fields get indexed for filtering in that namespace specifically* — omitting it means the namespace inherits the index's own metadata-indexing configuration, which for the managed indexes this skill covers is "index everything" by default (see "Filterable metadata isn't declared in the schema at all" in SKILL.md). There's rarely a reason to pass it explicitly unless you're deliberately restricting which fields are filterable in one namespace.
225
+
226
+
`__default__` is reserved — it always exists and can't be created or deleted; every namespace-taking call already defaults to it when `namespace` is omitted, but it's worth knowing when you see it show up unbidden in a `list_namespaces()` result.
227
+
228
+
### `describe_index_stats` — also now works
229
+
230
+
```python
231
+
stats = idx.describe_index_stats()
232
+
print(stats.total_vector_count, stats.namespaces) # total record count, namespace count
233
+
```
234
+
235
+
`describe_index_stats(filter=...)` is documented as rejected on every index type (there's no operation that returns a filtered count) — call it with no arguments.
236
+
195
237
## Integrating embedding providers
196
238
197
239
If your index has a dense or sparse vector field, you need embeddings. Three common paths:
@@ -256,8 +298,7 @@ This adapter also gives you a single chokepoint for retries, rate-limit backoff,
256
298
257
299
## Limits to be aware of
258
300
259
-
-**No bulk import (S3 import job)** for document-shaped indexes in `2026-01.alpha`. Load through `documents.upsert` / `documents.batch_upsert`.
301
+
-**No bulk import (S3 import job)** for document-shaped indexes in `2026-07`. Load through `documents.upsert` / `documents.batch_upsert`.
260
302
-**No backup/restore.** If you need recoverability, snapshot your source data, not the index.
261
-
-**No CMEK projects** — indexes can't be created in CMEK-enabled projects.
303
+
-**No CMEK projects alongside any `full_text_search` field** — such indexes can't be created in CMEK-enabled projects.
262
304
-**Indexing latency**: documents become searchable in ≲1 minute typically; multi-field schemas can take slightly longer.
0 commit comments