Skip to content

fix(vector_stores): handle vector=None in Milvus and Qdrant update methods#4568

Merged
kartik-mem0 merged 1 commit intomainfrom
fix/milvus-none-vector-update
Mar 27, 2026
Merged

fix(vector_stores): handle vector=None in Milvus and Qdrant update methods#4568
kartik-mem0 merged 1 commit intomainfrom
fix/milvus-none-vector-update

Conversation

@utkarsh240799
Copy link
Copy Markdown
Contributor

Summary

Closes #3708

Metadata-only updates (e.g. session ID changes at mem0/memory/main.py:670 and :1761) call vector_store.update(vector_id=..., vector=None, payload=...). This crashes on Milvus (DataNotMatchException — Milvus requires a valid float vector for every upsert) and Qdrant (ValidationErrorPointStruct rejects vector=None).

Problem

Milvus (mem0/vector_stores/milvus.py):
The update() method unconditionally builds {"id": vector_id, "vectors": vector, "metadata": payload} and passes it to client.upsert(). When vector=None, Milvus rejects the record because the vectors field expects FLOAT_VECTOR data.

Qdrant (mem0/vector_stores/qdrant.py):
The update() method constructs PointStruct(id=vector_id, vector=vector, payload=payload). Qdrant's Pydantic model validation rejects vector=None with 6 validation errors.

Both failures are triggered by the same call path — the session ID update logic in Memory.add() and AsyncMemory.add():

self.vector_store.update(
    vector_id=memory_id,
    vector=None,  # Keep same embeddings
    payload=updated_metadata,
)

Solution

Milvus: When vector or payload is None, fetch the existing record via client.get() and use its stored vector/metadata to fill in the missing values before calling upsert(). A single get() call is used even when both are None. Raises ValueError with clear messages if the record doesn't exist or has no vector data.

Qdrant: Use Qdrant's native partial-update APIs instead of a full upsert:

  • vector=Noneclient.set_payload() (metadata-only update)
  • payload=Noneclient.update_vectors() (vector-only update)
  • Both provided → client.upsert() (existing behavior, unchanged)

Other vector stores reviewed

Store Status
ChromaDB Already handles None natively
Pinecone Explicit if vector is not None guard
PGVector Truthiness check skips SQL UPDATE
FAISS Explicit if vector is not None guard
Redis Explicit guard for vector
Elasticsearch Explicit is not None guards

No other stores need fixes for this issue.

Testing

New tests added

Milvus (tests/vector_stores/test_milvus.py) — 5 new tests:

  • test_update_with_none_vector_fetches_existing — core bug fix: verifies existing vector is fetched and used when vector=None
  • test_update_with_none_payload_fetches_existing — verifies existing metadata is preserved when payload=None
  • test_update_with_both_none_fetches_existing — verifies only one client.get() call when both are None
  • test_update_with_none_vector_raises_on_missing_record — error handling for nonexistent records
  • test_update_with_none_vector_raises_on_missing_vector_data — error handling for corrupt records

Qdrant (tests/vector_stores/test_qdrant.py) — 3 new tests:

  • test_update_with_none_vector_uses_set_payload — verifies set_payload is called instead of upsert
  • test_update_with_none_payload_uses_update_vectors — verifies update_vectors is called instead of upsert
  • test_update_with_both_none_is_noop — verifies no client calls when both are None

Backward compatibility verified

  • All 14 pre-existing Milvus tests pass (including test_update_uses_upsert which confirms the normal both-provided path is unchanged)
  • All 77 pre-existing Qdrant tests pass (including test_update which confirms the normal both-provided path is unchanged)
  • Full test suite: 847 passed, 75 skipped, 0 regressions (2 pre-existing Redis failures from missing redis module)

🤖 Generated with Claude Code

…thods (#3708)

Metadata-only updates (e.g. session ID changes) pass vector=None to
vector_store.update(), which crashes on Milvus (DataNotMatchException)
and Qdrant (PointStruct ValidationError).

Milvus: fetch existing vector/payload via client.get() when either is
None, so upsert always receives valid data.

Qdrant: use native partial-update APIs (set_payload, update_vectors)
instead of full upsert when only one of vector/payload is provided.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

milvus AsyncMemory memory.add error

2 participants