{"record":{"id":"2b75085fdc52f4c4","repo":"chroma-core/chroma","slug":"expected-metadata-value-to-be-a-str-int-float-b-2b7508","errorCode":null,"errorMessage":"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}","messagePattern":"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1127,"sourceCode":"            f\"Expected metadata to be a dict or None, got {type(metadata)}\"\n        )\n    if metadata is None:\n        return metadata\n    if len(metadata) == 0:\n        raise ValueError(f\"Expected metadata to be a non-empty dict, got {metadata}\")\n    for key, value in metadata.items():\n        if not isinstance(key, str):\n            raise ValueError(f\"Expected metadata key to be a str, got {key}\")\n        # Check if value is a SparseVector (validation happens in __post_init__)\n        if isinstance(value, SparseVector):\n            pass  # Already validated in SparseVector.__post_init__\n        elif isinstance(value, list):\n            _validate_metadata_list_value(key, value)\n        # isinstance(True, int) evaluates to True, so we need to check for bools separately\n        elif not isinstance(value, bool) and not isinstance(\n            value, (str, int, float, type(None))\n        ):\n            raise ValueError(\n                f\"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}\"\n            )\n    return metadata\n\n\ndef serialize_metadata(metadata: Optional[Metadata]) -> Optional[Dict[str, Any]]:\n    \"\"\"Serialize metadata for transport, converting SparseVector dataclass instances to dicts.\n\n    Args:\n        metadata: Metadata dictionary that may contain SparseVector instances\n\n    Returns:\n        Metadata dictionary with SparseVector instances converted to transport format\n    \"\"\"\n    if metadata is None:\n        return None\n\n    result: Dict[str, Any] = {}","sourceCodeStart":1109,"sourceCodeEnd":1145,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1109-L1145","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert datetimes to isoformat strings or epoch floats, Decimals to float(), numpy scalars with .item()","Use None (not {} or []) to clear a metadata key on update","Reuse the same normalize_metadata() helper for add and update so both paths enforce identical rules"],"exampleFix":"# before\ncollection.update(ids=ids, metadatas=[{'ts': now, 'p': prob}])  # datetime, np.float32\n\n# after\ncollection.update(ids=ids, metadatas=[{'ts': now.isoformat(), 'p': float(prob)}])","handlingStrategy":"type-guard","validationCode":"from datetime import datetime, date\n\ndef normalize_update_value(v):\n    if isinstance(v, (datetime, date)):\n        return v.isoformat()\n    if hasattr(v, 'item'):\n        return v.item()\n    if isinstance(v, tuple):\n        return list(v)\n    return v\n\nmetas = [{k: normalize_update_value(v) for k, v in m.items()} for m in metas]\ncollection.update(ids=ids, metadatas=metas)","typeGuard":"def is_valid_update_value(v) -> bool:\n    if v is None or isinstance(v, (str, int, float, bool)):\n        return True\n    if isinstance(v, list) and v:\n        ts = {bool if isinstance(x, bool) else type(x) for x in v}\n        return len(ts) == 1 and next(iter(ts)) in (str, int, float, bool)\n    return False","tryCatchPattern":"try:\n    collection.update(ids=ids, metadatas=metas)\nexcept ValueError as e:\n    if 'Expected metadata value to be a str, int, float, bool, SparseVector, list, or None' in str(e):\n        metas = [{k: normalize_update_value(v) for k, v in m.items()} for m in metas]\n        collection.update(ids=ids, metadatas=metas)\n    else:\n        raise","preventionTips":["Share one normalize_metadata() between add and update paths","Use None to delete a key on update, not {} or []","Convert datetime/Decimal/numpy values before they reach Chroma"],"tags":["chromadb","python","metadata","update","type-safety","validation"],"backgroundTag":"unsupported-metadata-value-type","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}