Skip to content

Langchain vector store: list() returns None for any non-Chroma VectorStore client (FAISS, Qdrant, PGVector, ...), crashing Memory.get_all() and Memory.delete_all() with TypeError #7461

Open

Description

@BlueX888

Component

Vector Store

Summary

Langchain.list() (mem0/vector_stores/langchain.py:191) gates every return behind hasattr(self.client, "_collection") (mem0/vector_stores/langchain.py:196). For any langchain VectorStore that is not Chroma-backed (FAISS, Qdrant, PGVector, ...), the function falls off the end and returns None — not even []. LangchainConfig.client is typed as "Existing VectorStore instance" (mem0/configs/vector_stores/langchain.py:16), so any langchain VectorStore — not only Chroma — is a supported configuration, and every non-Chroma client breaks Memory.get_all() and Memory.delete_all():

  • Memory.get_all() iterates the result at mem0/memory/main.py:1354 (for mem in actual_memories:) → TypeError: 'NoneType' object is not iterable
  • Memory.delete_all() indexes it at mem0/memory/main.py:1923-1925 (self.vector_store.list(filters=filters, top_k=DELETE_ALL_BATCH_SIZE)[0]) → TypeError: 'NoneType' object is not subscriptable

Steps to Reproduce

Fully offline (no API key, no network; FakeEmbeddings avoids any model calls):

from langchain_community.vectorstores import FAISS
from langchain_core.embeddings import FakeEmbeddings

from mem0 import Memory

faiss_store = FAISS.from_texts(["hello", "world"], embedding=FakeEmbeddings(size=8))
print("FAISS store has '_collection' attr:", hasattr(faiss_store, "_collection"))

config = {
    "vector_store": {
        "provider": "langchain",
        "config": {"client": faiss_store, "collection_name": "mem0"},
    },
    "embedder": {"provider": "openai",
                 "config": {"api_key": "dummy", "embedding_dims": 1536}},
    "llm": {"provider": "openai",
            "config": {"api_key": "dummy", "model": "gpt-4o-mini"}},
}

m = Memory.from_config(config)

print("vector_store.list() returns:", m.vector_store.list())

try:
    m.get_all(filters={"user_id": "alice"})
    print("get_all(): succeeded")
except Exception as e:
    print(f"get_all() raised {type(e).__name__}: {e}")

try:
    m.delete_all(user_id="alice")
    print("delete_all(): succeeded")
except Exception as e:
    print(f"delete_all() raised {type(e).__name__}: {e}")

# control: a real Chroma client through the same Langchain wrapper
import chromadb
from langchain_community.vectorstores import Chroma

print()
print("=== control: real Chroma client through the same Langchain store ===")

chroma_store = Chroma(collection_name="mem0",
                      embedding_function=FakeEmbeddings(size=8),
                      client=chromadb.EphemeralClient())
print("Chroma store has '_collection' attr:", hasattr(chroma_store, "_collection"))

m2 = Memory.from_config({**config, "vector_store": {
    "provider": "langchain",
    "config": {"client": chroma_store, "collection_name": "mem0"}}})
print("vector_store.list() returns:", m2.vector_store.list())
print("get_all():", m2.get_all(filters={"user_id": "alice"}))
print("delete_all() count:", m2.delete_all(user_id="alice"))

Expected Behavior

list() returns a (nested) list for every client, as VectorStoreBase.list() documents — "List all memories." (mem0/vector_stores/base.py:59-61) — and as every sibling implementation does: faiss.py:619, cassandra.py:480, opensearch.py:408, mongodb.py:413 all return a nested list (return [[]]) even when the store is empty. With that, get_all() returns {'results': []} and delete_all() completes, exactly as the Chroma control group does.

Actual Behavior

Actual output (mem0 2.0.20 from source @ c7ee362a, Python 3.12.14, macOS; langchain 1.4.2, langchain-community 0.4.2, faiss-cpu 1.15.1, chromadb 1.5.9):

FAISS store has '_collection' attr: False
vector_store.list() returns: None
get_all() raised TypeError: 'NoneType' object is not iterable
delete_all() raised TypeError: 'NoneType' object is not subscriptable

=== control: real Chroma client through the same Langchain store ===
Chroma store has '_collection' attr: True
vector_store.list() returns: [[]]
get_all(): {'results': []}
delete_all() count: {'message': 'Memories deleted successfully!'}

The tracebacks land at mem0/memory/main.py:1354 (for mem in actual_memories: in the get_all path) and mem0/memory/main.py:1925 (the )[0] in delete_all).

Root Cause

mem0/vector_stores/langchain.py:191-208: every return inside list() sits either inside the if hasattr(self.client, "_collection") and hasattr(self.client._collection, "get") branch or inside the except. For a non-Chroma client the try block completes without raising and without executing any return, so the function falls off the end and Python returns None. Non-Chroma langchain clients expose no generic "list all vectors" API, so the correct behaviour is the sibling convention: return an empty nested list, return [[]] (a flat [] would still make delete_all()'s unconditional [0] at main.py:1925 raise IndexError).

Environment

  • mem0 version: 2.0.20 (from source, main @ c7ee362a)
  • Python 3.12.14, macOS (darwin, arm64)
  • langchain 1.4.2, langchain-community 0.4.2, langchain-core 1.6.5, faiss-cpu 1.15.1, chromadb 1.5.9

How You Verified This

What I Ran

The exact script above, offline, against the unpatched main @ c7ee362a: a FAISS VectorStore built locally with FakeEmbeddings, wrapped in Memory via vector_store.provider = "langchain"; then the same wrapper around a real (ephemeral) Chroma client as a control. I also added a regression test that wraps a Mock(spec=VectorStore) (no _collection attribute, like every non-Chroma client) and asserts list() returns a list whose first element is a list.

What I Saw

The verbatim output pasted in "Actual Behavior": list() returns None on the FAISS client, get_all() raises TypeError: 'NoneType' object is not iterable, delete_all() raises TypeError: 'NoneType' object is not subscriptable; the Chroma control returns [[]] and both methods succeed. The regression test fails on the unpatched base:

E       AssertionError: expected a list, got <class 'NoneType'>
FAILED tests/vector_stores/test_langchain_vector_store.py::test_list_returns_empty_list_for_non_chroma_client

Why This Is a Bug

VectorStoreBase.list()'s contract is "List all memories." (mem0/vector_stores/base.py:59-61), and the consumers in main.py assume a list (for mem in actual_memories: at 1354, unconditional [0] at 1925). Every sibling vector store returns a nested list even when empty (faiss.py:619, cassandra.py:480, opensearch.py:408, mongodb.py:413). LangchainConfig.client is typed as "Existing VectorStore instance" (mem0/configs/vector_stores/langchain.py:16) — any langchain VectorStore, not only Chroma — so the non-Chroma path is supported configuration, and it returns None.

What I Ruled Out

  • Not a FAISS-specific quirk: the gate is hasattr(self.client, "_collection"), so any non-Chroma langchain client (Qdrant, PGVector, ...) takes the same fall-through; only Chroma-backed clients carry _collection.
  • Not the except branch: for a non-Chroma client the try block completes without raising — the function simply has no return left on that path.
  • Not caller misuse: the config path used is the documented provider: "langchain" + client wiring, and the identical wiring with a Chroma client works.

With the one-line return [[]] fix I re-ran the script: list() returns [[]], get_all() returns {'results': []}, delete_all() succeeds, and tests/vector_stores/test_langchain_vector_store.py passes (15 passed, 1 warning in 0.33s, including the regression test).

Happy to open a PR with this fix once this issue is accepted, or to be assigned if you prefer to take it.

AI Assistance

AI found and wrote this, and I have not reproduced it myself — the reproduction output above is captured verbatim from actual runs of the script and tests by the AI tooling.

Activity

  1. BlueX888 commented on Sep 26, 2026

    @BlueX888
    Author

    Reproduced this. Root cause: mem0/vector_stores/langchain.py:196 gates every return in list() behind the Chroma-only hasattr(self.client, "_collection") check, so any non-Chroma VectorStore (FAISS, Qdrant, PGVector, ...) falls off the end and returns None; Memory.get_all() then crashes iterating it (mem0/memory/main.py:1354) and Memory.delete_all() crashes indexing it (mem0/memory/main.py:1925).

    Minimal reproduction:

    cd /tmp/lvs-base && /tmp/lvs-venv/bin/python /Users/admin/.claude/oss-contributor/work/repro_langchain_list_none.py (the script from the issue, run offline against mem0 from unpatched source @ c7ee362a)
    

    Observed output:

    FAISS store has '_collection' attr: False
    vector_store.list() returns: None
    get_all() raised TypeError: 'NoneType' object is not iterable
    delete_all() raised TypeError: 'NoneType' object is not subscriptable
    

    Fix is ready on BlueX888:fix/prep-langchain-vector-store-list-returns-none (commit 55cc1423). Return [[]] on the non-Chroma path, matching the empty-result convention of the sibling vector stores (faiss.py:619, cassandra.py:480, opensearch.py:408, mongodb.py:413), so get_all()/delete_all() see an empty result instead of None.
    Verified with: python -m pytest tests/vector_stores/test_langchain_vector_store.py (15 passed, incl. the new regression test test_list_returns_empty_list_for_non_chroma_client)

    Happy to open the PR once you confirm the approach or assign this to me.

  2. chenhz01 commented on Sep 30, 2026

    @chenhz01

    Hi @BlueX888 - the Langchain.list() gate on hasattr(self.client, "_collection") is the culprit: any non-Chroma langchain store (FAISS, Qdrant, PGVector) falls through and returns None, so get_all() iterates None and delete_all() indexes None[0] - both TypeError.

    Here is a wrapper that always returns a list:

    # mem0's Langchain.list() returns None for any non-Chroma client, crashing
    # get_all()/delete_all(). Wrap the client so list() always returns a list.
    # Plug in your own langchain VectorStore instance.
    def list_memories(vstore) -> list:
        if hasattr(vstore, "_collection"):           # Chroma path mem0 already handles
            return vstore._collection.get()["ids"]
        ds = getattr(vstore, "docstore", None)
        if ds is not None:
            return list(ds._dict.keys()) if hasattr(ds, "_dict") else list(ds.yield_keys())
        if hasattr(vstore, "index_to_docstore_id"):
            return list(vstore.index_to_docstore_id.values())
        return []
    
    if __name__ == "__main__":
        class FakeFAISS:
            index_to_docstore_id = {0: "m1", 1: "m2"}
        print(list_memories(FakeFAISS()))   # ['m1', 'm2']  (never None)

    It uses Chroma's _collection when present, else falls back to the store's docstore / index_to_docstore_id. If useful I can extend it to reconstruct minimal payloads for delete_all().

  3. BlueX888 commented on Oct 17, 2026

    @BlueX888
    Author

    Correction to my earlier direction here: an unconditional empty-result fallback is itself the bug — it makes get_all() return {'results': []} even when matching records exist and lets delete_all() report success while deleting nothing (exactly what @YS-OH-CORE demonstrated on #7439). I've reworked the branch accordingly:

    • Langchain.list() now dispatches on the actual client, per @O96a's proper-fix note: the Chroma path is unchanged (empty result stays [[]], listing errors are logged and re-raised instead of swallowed); docstore-backed clients (FAISS and friends) are really enumerated — (key, doc) pairs from the docstore, mem0 filters applied with faiss.py _apply_filters semantics before the top_k cap — so get_all() returns the matching memories and delete_all() actually deletes them; every other client raises NotImplementedError naming the client type, raised outside the try so the broad Chroma except can't swallow it.
    • Tests now pin the populated-store behavior the fallback was hiding: real offline FAISS with 3 alice + 1 bob records (list returns the 3 alice entries with docstore-key ids; Memory.get_all returns them instead of {'results': []}; delete_all removes exactly alice and leaves bob), top_k capping/uncapping, correct-empty [[]] for a genuinely empty store, and NotImplementedError for unenumerable clients. The old test that pinned the wrong-empty behavior is gone.

    Holding off on opening the PR until #7439 picks up the accepted label so the triage gate doesn't close it (it closed #7464 for exactly that). Branch: fix/prep-langchain-vector-store-list-returns-none (rebased on current main).

  4. BlueX888 commented on Oct 17, 2026

    @BlueX888
    Author

    Quick clarification on my previous comment so nobody chases a stale branch: the reworked implementation (three-way dispatch on the actual client — Chroma path kept, docstore-backed clients like FAISS enumerated from their real docstore, unenumerable clients raising NotImplementedError named for the client type) is prepared and validated with red/green tests against current main (0724291), but the branch is deliberately not pushed yet: this repo's PR gate auto-closes PRs whose referenced issue lacks the maintainer accepted label (see #7464). I'll push and open the PR referencing Fixes #7439 as soon as this issue is triaged (assigned or labeled accepted/prs welcome).

  5. O96a commented on Oct 20, 2026

    @O96a

    The _collection check is Chroma internals leaking into a generic wrapper — langchain's VectorStore base class has no list-all method, so there's nothing portable to duck-type on, and every non-Chroma backend falls straight through to None.

    Two ways out. Per-backend branches work — Chroma _collection.get(include=[]), Qdrant client.scroll(limit=..., offset=...), PGVector a plain SELECT id, payload — but that's exactly the maintenance tax you're trying to avoid. I'd take the other route: keep mem0's own id→payload sidecar (the same store already written on insert) and have list() / delete_all() read from that, then hand the ids to client.delete(ids=...). Enumeration is a metadata problem, not a similarity-search problem, so it shouldn't go through the vector client at all.

    Either way, list() should raise a clear NotImplementedError when a backend genuinely can't enumerate. A silent None that turns into TypeError: 'NoneType' is not iterable three frames later is the worst outcome here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    vector-storeVector store backends (Qdrant, PGVector, Redis, etc.)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions