run-llama/llama_index · error · ValueError

Metadata key must be str!

Error message

Metadata key must be str!

What it means

`_validate_is_flat_dict` enforces that node metadata sent to a vector store is a flat dict with string keys. A non-str key can appear when integers or other hashables are used as dict keys (JSON round-trips sometimes introduce these), which most vector stores cannot index. The check runs inside `node_to_metadata_dict` with flat_metadata=True, so it fires at ingestion time, before anything is persisted.

Source

Thrown at llama-index-core/llama_index/core/vector_stores/utils.py:33

    FilterOperator,
    FilterCondition,
)


DEFAULT_TEXT_KEY = "text"
DEFAULT_TEXT_RESOURCE_KEY = "text_resource"
DEFAULT_EMBEDDING_KEY = "embedding"
DEFAULT_DOC_ID_KEY = "doc_id"


def _validate_is_flat_dict(metadata_dict: dict) -> None:
    """
    Validate that metadata dict is flat,
    and key is str, and value is one of (str, int, float, None).
    """
    for key, val in metadata_dict.items():
        if not isinstance(key, str):
            raise ValueError("Metadata key must be str!")
        if not isinstance(val, (str, int, float, type(None))):
            raise ValueError(
                f"Value for metadata {key} must be one of (str, int, float, None)"
            )


def node_to_metadata_dict(
    node: BaseNode,
    remove_text: bool = False,
    text_field: str = DEFAULT_TEXT_KEY,
    text_resource_field: str = DEFAULT_TEXT_RESOURCE_KEY,
    flat_metadata: bool = False,
) -> Dict[str, Any]:
    """Common logic for saving Node data into metadata dict."""
    # Using mode="json" here because BaseNode may have fields of type bytes (e.g. images in ImageBlock),
    # which would cause serialization issues.
    node_dict = node.model_dump(mode="json")
    metadata: Dict[str, Any] = node_dict.get("metadata", {})

View on GitHub (pinned to afd0fef371)

Solutions

  1. Coerce all metadata keys to strings when building nodes: `{str(k): v for k, v in metadata.items()}`.
  2. Fix the upstream data source so keys are emitted as strings (e.g. `json.load(..., object_keys_hook=...)` or dataframe `astype(str)` on key columns).
  3. Add a validation step in your ingestion pipeline that rejects non-str keys early with a clearer message.

Example fix

# before
node = TextNode(text="...", metadata={101: "intro"})  # int key

# after
node = TextNode(text="...", metadata={str(k): v for k, v in raw_meta.items()})
Defensive patterns

Strategy: validation

Validate before calling

def normalize_metadata_keys(metadata: dict) -> dict:
    bad = [k for k in metadata if not isinstance(k, str)]
    if bad:
        raise TypeError(f"non-str metadata keys: {bad!r}")
    return metadata

Type guard

def has_str_keys(metadata: dict) -> bool:
    return all(isinstance(k, str) for k in metadata)

Try / catch

try:
    store.add(nodes)
except ValueError as e:
    if "key must be str" in str(e):
        nodes = [n.with metadata normalized]  # fix keys, then retry once
    raise

Prevention

When it happens

Trigger: Adding nodes whose `metadata` dict contains non-string keys (e.g. `{1: "chapter"}`) to a store that validates flat metadata; building Document/TextNode objects from pandas rows or JSON where numeric dict keys survive.

Common situations: Loading metadata from JSON files parsed with integer-like keys; converting dataframe columns to dicts and using column positions as keys; programmatic metadata builders that accept arbitrary dicts; Pydantic models dumping enums or other non-str key types.

Related errors


AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15). Data as JSON: /api/errors/d597e794bf5b6136. Report an issue: GitHub.