chroma-core/chroma · error · ValueError

Expected metadata value to be a str, int, float, bool, Spars

Error message

Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}

What it means

Per-record metadata values on update must be str, int, float, bool, None, a SparseVector, or a homogeneous list of scalars - the same flat rule as the insert path, checked by validate_update_metadata (chromadb/api/types.py:1127, reached from collection.update via chromadb/segment.py:407). None is allowed and means 'delete this key'. Nested dicts, datetime, Decimal, tuples, sets and numpy scalars raise this error.

Source

Thrown at chromadb/api/types.py:1127

            f"Expected metadata to be a dict or None, got {type(metadata)}"
        )
    if metadata is None:
        return metadata
    if len(metadata) == 0:
        raise ValueError(f"Expected metadata to be a non-empty dict, got {metadata}")
    for key, value in metadata.items():
        if not isinstance(key, str):
            raise ValueError(f"Expected metadata key to be a str, got {key}")
        # Check if value is a SparseVector (validation happens in __post_init__)
        if isinstance(value, SparseVector):
            pass  # Already validated in SparseVector.__post_init__
        elif isinstance(value, list):
            _validate_metadata_list_value(key, value)
        # isinstance(True, int) evaluates to True, so we need to check for bools separately
        elif not isinstance(value, bool) and not isinstance(
            value, (str, int, float, type(None))
        ):
            raise ValueError(
                f"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}"
            )
    return metadata


def serialize_metadata(metadata: Optional[Metadata]) -> Optional[Dict[str, Any]]:
    """Serialize metadata for transport, converting SparseVector dataclass instances to dicts.

    Args:
        metadata: Metadata dictionary that may contain SparseVector instances

    Returns:
        Metadata dictionary with SparseVector instances converted to transport format
    """
    if metadata is None:
        return None

    result: Dict[str, Any] = {}

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Convert datetimes to isoformat strings or epoch floats, Decimals to float(), numpy scalars with .item()
  2. Use None (not {} or []) to clear a metadata key on update
  3. Reuse the same normalize_metadata() helper for add and update so both paths enforce identical rules

Example fix

# before
collection.update(ids=ids, metadatas=[{'ts': now, 'p': prob}])  # datetime, np.float32

# after
collection.update(ids=ids, metadatas=[{'ts': now.isoformat(), 'p': float(prob)}])
Defensive patterns

Strategy: type-guard

Validate before calling

from datetime import datetime, date

def normalize_update_value(v):
    if isinstance(v, (datetime, date)):
        return v.isoformat()
    if hasattr(v, 'item'):
        return v.item()
    if isinstance(v, tuple):
        return list(v)
    return v

metas = [{k: normalize_update_value(v) for k, v in m.items()} for m in metas]
collection.update(ids=ids, metadatas=metas)

Type guard

def is_valid_update_value(v) -> bool:
    if v is None or isinstance(v, (str, int, float, bool)):
        return True
    if isinstance(v, list) and v:
        ts = {bool if isinstance(x, bool) else type(x) for x in v}
        return len(ts) == 1 and next(iter(ts)) in (str, int, float, bool)
    return False

Try / catch

try:
    collection.update(ids=ids, metadatas=metas)
except ValueError as e:
    if 'Expected metadata value to be a str, int, float, bool, SparseVector, list, or None' in str(e):
        metas = [{k: normalize_update_value(v) for k, v in m.items()} for m in metas]
        collection.update(ids=ids, metadatas=metas)
    else:
        raise

Prevention

When it happens

Trigger: collection.update(ids=..., metadatas=[{'ts': datetime.now()}]); {'conf': np.float32(0.9)}; {'obj': {'a': 1}}; {'tags': ('a','b')} (tuple is not a list); writing ORM or pandas values back without normalization.

Common situations: Updating ORM/pandas objects with datetime or Decimal columns; updating probabilities held as numpy scalars; assuming tuples count as lists; reusing insert-path payloads that were never normalized.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/2b75085fdc52f4c4. Report an issue: GitHub.