langchain-ai/langchain · error · TypeError

Vectorstore should be either a VectorStore or a DocumentInde

Error message

Vectorstore should be either a VectorStore or a DocumentIndex. Got {type(destination)}.

What it means

Raised in `index()` when the destination object is neither a `VectorStore` nor a `DocumentIndex`. The indexer must upsert and delete documents through one of those two protocols; passing a retriever, a bare client, a string DSN, or a duck-typed object that never subclasses the right base class results in a `TypeError`. The type annotation makes this unreachable for typed callers, but dynamic code bypasses it.

Source

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

            if not hasattr(destination, method):
                msg = (
                    f"Vectorstore {destination} does not have required method {method}"
                )
                raise ValueError(msg)

        if type(destination).delete == VectorStore.delete:
            # Checking if the VectorStore has overridden the default delete method
            # implementation which just raises a NotImplementedError
            msg = "Vectorstore has not implemented the delete method"
            raise ValueError(msg)
    elif isinstance(destination, DocumentIndex):
        pass
    else:
        msg = (  # type: ignore[unreachable]
            f"Vectorstore should be either a VectorStore or a DocumentIndex. "
            f"Got {type(destination)}."
        )
        raise TypeError(msg)

    if isinstance(docs_source, BaseLoader):
        try:
            doc_iterator = docs_source.lazy_load()
        except NotImplementedError:
            doc_iterator = iter(docs_source.load())
    else:
        doc_iterator = iter(docs_source)

    source_id_assigner = _get_source_id_assigner(source_id_key)

    # Mark when the update started.
    index_start_dt = record_manager.get_time()
    num_added = 0
    num_skipped = 0
    num_updated = 0
    num_deleted = 0
    scoped_full_cleanup_source_ids: set[str] = set()

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Pass the LangChain `VectorStore` wrapper object itself (e.g. the `Chroma` instance, not its `_collection`).
  2. Make custom destinations subclass `VectorStore` or implement `DocumentIndex`.
  3. If given a retriever, recover the store via its `vectorstore` attribute where available.

Example fix

# before
index(chroma._collection, docs, rm, cleanup="full")

# after
index(chroma, docs, rm, cleanup="full")
Defensive patterns

Strategy: type-guard

Validate before calling

from langchain_core.vectorstores import VectorStore

assert isinstance(destination, VectorStore), (
    f"expected VectorStore, got {type(destination).__name__}; "
    "pass the LangChain wrapper, not a retriever or raw client"
)
index(destination, docs, rm, cleanup="full")

Type guard

def is_indexing_destination(obj) -> bool:
    from langchain_core.vectorstores import VectorStore
    from langchain_core.indexing.api import DocumentIndex
    return isinstance(obj, (VectorStore, DocumentIndex))

Prevention

When it happens

Trigger: Calling `index(my_retriever, docs, rm)`; passing `vectorstore.as_retriever()` output; passing the underlying client (e.g. a `chromadb.Collection`) instead of the LangChain wrapper; test mocks without proper subclassing.

Common situations: Confusing retrievers with stores; grabbing low-level SDK clients from LangChain integrations (`store._collection`); refactors that swap the store for a facade object.

Related errors


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