langchain-ai/langchain · error · ValueError

source_id_key should be either None, a string or a callable.

Error message

source_id_key should be either None, a string or a callable. Got {source_id_key} of type {type(source_id_key)}.

What it means

Raised by `_get_source_id_assigner` in `langchain_core.indexing.api`. During indexing with incremental/scoped_full cleanup, each Document must be traced back to an upstream source (e.g. a file path or URL) so stale copies can be deleted. The `source_id_key` parameter tells the indexer how to extract that ID and must be None, a metadata key name, or a callable — anything else is rejected.

Source

Thrown at libs/core/langchain_core/indexing/api.py:136

    if batch:
        yield batch


def _get_source_id_assigner(
    source_id_key: str | Callable[[Document], str] | None,
) -> Callable[[Document], str | None]:
    """Get the source id from the document."""
    if source_id_key is None:
        return lambda _doc: None
    if isinstance(source_id_key, str):
        return lambda doc: doc.metadata[source_id_key]
    if callable(source_id_key):
        return source_id_key
    msg = (  # type: ignore[unreachable]
        f"source_id_key should be either None, a string or a callable. "
        f"Got {source_id_key} of type {type(source_id_key)}."
    )
    raise ValueError(msg)


def _deduplicate_in_order(
    hashed_documents: Iterable[Document],
) -> Iterator[Document]:
    """Deduplicate a list of hashed documents while preserving order."""
    seen: set[str] = set()

    for hashed_doc in hashed_documents:
        if hashed_doc.id not in seen:
            # At this stage, the id is guaranteed to be a string.
            # Avoiding unnecessary run time checks.
            seen.add(cast("str", hashed_doc.id))
            yield hashed_doc


class IndexingException(LangChainException):
    """Raised when an indexing operation fails."""

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Use a metadata key string: `source_id_key="source"` and ensure each document's metadata contains it.
  2. Or use a callable: `source_id_key=lambda doc: doc.metadata["file_path"]`.
  3. Or pass None when using cleanup=None/'full', which does not need source IDs.

Example fix

# before
index(vs, docs, rm, source_id_key=0, cleanup="incremental")

# after
index(vs, docs, rm, source_id_key="source", cleanup="incremental")
Defensive patterns

Strategy: type-guard

Validate before calling

if source_id_key is not None and not isinstance(source_id_key, (str,)) and not callable(source_id_key):
    raise TypeError(f"bad source_id_key: {source_id_key!r}")
index(vs, docs, rm, source_id_key=source_id_key, cleanup="incremental")

Type guard

def is_valid_source_id_key(k) -> bool:
    return k is None or isinstance(k, str) or callable(k)

Prevention

When it happens

Trigger: Passing `source_id_key=123`, a tuple, or a list to `index()`/`aindex()`; passing a dict-like object that is not a str; passing a non-callable object that was intended to be a function (e.g. referencing an attribute instead of the method).

Common situations: Config-driven pipelines where source_id_key comes from YAML/JSON and is parsed as a non-string type; typos like `source_id_key=doc.metadata["source"]` (evaluates to a value, but if the metadata value is not a str — e.g. an int — this error fires at a different layer; here the error is on the key parameter itself).

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/e1b284313a6877db. Report an issue: GitHub.