cocoindex-io/cocoindex · error · ValueError

Row {row.id!r}: attribute name {k!r} is reserved.

Error message

Row {row.id!r}: attribute name {k!r} is reserved.

What it means

In turbopuffer's wire format, the row id and all vector field names live at the top level of the upsert payload, so a user attribute with one of those names would collide. `_row_to_upsert` builds `reserved = {"id"} | vector_field_names` and raises this ValueError if any key in `row.attributes` matches, preventing silent data corruption on write.

Source

Thrown at python/cocoindex/connectors/turbopuffer/_target.py:228

            raise ValueError(
                f"Row {row.id!r}: missing vector fields {sorted(missing)}."
            )
        for name, vec in row.vector.items():
            out[name] = _vector_to_list(vec)
    else:
        vector_field_names = {_DEFAULT_VECTOR_FIELD}
        if isinstance(row.vector, dict):
            raise ValueError(
                f"Row {row.id!r}: schema declares a single unnamed vector but "
                f"row.vector is a dict."
            )
        out[_DEFAULT_VECTOR_FIELD] = _vector_to_list(row.vector)

    reserved = {"id"} | vector_field_names
    if row.attributes:
        for k, v in row.attributes.items():
            if k in reserved:
                raise ValueError(f"Row {row.id!r}: attribute name {k!r} is reserved.")
            out[k] = v

    return out


def _vector_type_str(vs: res_schema.VectorSchema) -> str:
    """Render a VectorSchema as turbopuffer's ``[N]fXX`` type string."""
    dt = np.dtype(vs.dtype)
    if dt == np.float32:
        suffix = "f32"
    elif dt == np.float16:
        suffix = "f16"
    else:
        raise ValueError(
            f"Turbopuffer vectors only support float32 or float16, got {dt}."
        )
    return f"[{vs.size}]{suffix}"

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Rename the attribute key to something not in {"id"} ∪ declared vector field names, e.g. "doc_id" or "source_id".
  2. Filter or remap reserved keys before constructing Row: drop or prefix them in your mapping function.
  3. If the attribute genuinely duplicates the row id, just remove it — the id is already written at the top level.

Example fix

// before
Row(id=doc["id"], vector=vec, attributes=doc)  # doc contains 'id' and 'title_vec'

// after
attrs = {k: v for k, v in doc.items() if k not in {"id", "title_vec"}}
Row(id=doc["id"], vector=vec, attributes=attrs)
Defensive patterns

Strategy: validation

Validate before calling

reserved = {"id"} | set(schema.vectors.vectors)
bad = set(row.attributes or {}) & reserved
assert not bad, f"Reserved attribute keys: {bad}"

Try / catch

try:
    await component.reconcile(row)
except ValueError as e:
    m = re.search(r"attribute name (.+?) is reserved", str(e))
    if m:
        row = rename_attribute(row, ast.literal_eval(m.group(1)))
    else:
        raise

Prevention

When it happens

Trigger: Row(id=1, vector=..., attributes={"id": "x"}) or attributes containing a key equal to a declared vector field name (e.g. "title_vec") when reconcile serializes the row.

Common situations: Storing source document metadata that includes an 'id' or 'vector'-like field; bulk-uploading records whose field names came from a CSV/JSON that already contains 'id'; accidentally dumping whole objects into attributes instead of picking non-reserved keys.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/19cde31f8b90edd0. Report an issue: GitHub.