chroma-core/chroma · error · ValueError

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

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.

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.

Appendix: 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)