fix(tests): update 35 failing tests for v3 single-pass pipeline
Tests were written for the old two-pass extraction pipeline (extract
facts + merge with ADD/UPDATE/DELETE). V3 uses single-pass ADD-only
extraction with one LLM call returning {"memory": [...]}.
Changes by category:
Old pipeline tests (13 failures):
- Delete TestHallucinatedIdGuard entirely (tests UPDATE/DELETE ID
resolution that no longer exists)
- Update error log assertions to match v3 log messages
- Change LLM mock from 2-call side_effect to 1-call return_value
- Update vllm thinking tag tests for single-call format
ensure_json_instruction removed (3):
- Delete source-inspection tests (function still exists in utils
but is no longer called from _add_to_vector_store)
UTC timestamp normalization removed (2):
- Update expected timestamps to stored-as-is (no UTC conversion)
- Remove _normalize_iso_timestamp_to_utc import
_search_vector_store signature: top_k -> limit (2):
- Update test calls to use limit= parameter
Context manager + close/db removed (6):
- Delete tests for __enter__/__exit__/close/db that were removed
Vector store BM25 config (3):
- Qdrant: add sparse_vectors_config to create_col assertion,
named vector format {"": vector} in update assertion
- MongoDB: assert_any_call for both vector + text search indexes
Misc (6):
- test_search_handles_incomplete_payloads: expect 1 result (v3
filters entries without 'data' key)
- Embedding cache tests: update for embed_batch (1 call) vs
individual embed (was 2 calls)
- Graph reset tests: delete (graph support removed)
- use_azure_credential: update expected sensitivity to True
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2a5ffd0976
commit
0b4bbcefff
+31
-78
@@ -137,11 +137,10 @@ def test_search_handles_incomplete_payloads(mock_sqlite, mock_llm_factory, mock_
|
||||
|
||||
result = memory._search_vector_store("test", {"user_id": "test"}, 10)
|
||||
|
||||
assert len(result) == 2
|
||||
memories_by_id = {mem["id"]: mem for mem in result}
|
||||
|
||||
assert memories_by_id["mem_1"]["memory"] == ""
|
||||
assert memories_by_id["mem_2"]["memory"] == "content"
|
||||
# v3 search pipeline skips entries where payload has no "data" key
|
||||
assert len(result) == 1
|
||||
assert result[0]["id"] == "mem_2"
|
||||
assert result[0]["memory"] == "content"
|
||||
|
||||
|
||||
@patch('mem0.utils.factory.EmbedderFactory.create')
|
||||
@@ -497,14 +496,16 @@ def test_add_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm_fa
|
||||
telemetry_vector_store = MagicMock()
|
||||
mock_vector_factory.side_effect = [mock_vector_store, telemetry_vector_store]
|
||||
|
||||
# LLM extracts fact "User likes Python", then ADD action rewrites to "The user enjoys Python"
|
||||
# V3 single-call extraction: LLM returns extracted memories directly
|
||||
mock_llm = MagicMock()
|
||||
mock_llm.generate_response.side_effect = [
|
||||
json.dumps({"facts": ["User likes Python"]}),
|
||||
json.dumps({"memory": [{"id": "0", "text": "The user enjoys Python", "event": "ADD", "old_memory": None}]}),
|
||||
]
|
||||
mock_llm.generate_response.return_value = json.dumps(
|
||||
{"memory": [{"text": "The user enjoys Python"}]}
|
||||
)
|
||||
mock_llm_factory.return_value = mock_llm
|
||||
|
||||
# embed_batch is used in Phase 3 for all extracted memories
|
||||
embedder.embed_batch.return_value = [[0.4, 0.5, 0.6]]
|
||||
|
||||
mock_sqlite.return_value = MagicMock()
|
||||
|
||||
from mem0.memory.main import Memory as MemoryClass
|
||||
@@ -512,11 +513,10 @@ def test_add_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm_fa
|
||||
|
||||
memory.add("I like Python", user_id="test_user", infer=True)
|
||||
|
||||
# embed should be called exactly twice:
|
||||
# 1. For the extracted fact "User likes Python" (search)
|
||||
# 2. For the rewritten text "The user enjoys Python" (pre-cached before _create_memory)
|
||||
# It should NOT be called a 3rd time inside _create_memory
|
||||
assert embedder.embed.call_count == 2
|
||||
# V3 pipeline: embed called once for search query (Phase 1),
|
||||
# embed_batch called once for extracted memories (Phase 3)
|
||||
assert embedder.embed.call_count == 1
|
||||
assert embedder.embed_batch.call_count == 1
|
||||
mock_vector_store.insert.assert_called_once()
|
||||
|
||||
|
||||
@@ -526,15 +526,15 @@ def test_add_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm_fa
|
||||
@patch('mem0.memory.storage.SQLiteManager')
|
||||
def test_update_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory):
|
||||
"""
|
||||
Regression test for issue #3723 (infer=True UPDATE path): when the LLM rewrites a fact during
|
||||
an UPDATE action, the embedding should be computed once and cached, not computed again inside _update_memory.
|
||||
Regression test for issue #3723 (infer=True path): V3 is ADD-only, so this test verifies
|
||||
that the single-call extraction pipeline embeds via embed_batch, not individual embed calls.
|
||||
"""
|
||||
embedder = MagicMock()
|
||||
embedder.embed.return_value = [0.1, 0.2, 0.3]
|
||||
embedder.config = MagicMock(embedding_dims=3)
|
||||
mock_embedder_factory.return_value = embedder
|
||||
|
||||
# Existing memory that will be matched for update
|
||||
# Existing memory that will be returned from search
|
||||
existing_memory = MockVectorMemory(
|
||||
memory_id="existing-mem-id",
|
||||
payload={
|
||||
@@ -549,17 +549,20 @@ def test_update_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm
|
||||
mock_vector_store.get.return_value = existing_memory
|
||||
mock_vector_store.insert.return_value = None
|
||||
mock_vector_store.update.return_value = None
|
||||
mock_vector_store.keyword_search.return_value = None
|
||||
telemetry_vector_store = MagicMock()
|
||||
mock_vector_factory.side_effect = [mock_vector_store, telemetry_vector_store]
|
||||
|
||||
# LLM extracts fact "User loves Python now", then UPDATE action rewrites to "The user loves Python"
|
||||
# V3 single-call extraction: LLM returns extracted memories directly
|
||||
mock_llm = MagicMock()
|
||||
mock_llm.generate_response.side_effect = [
|
||||
json.dumps({"facts": ["User loves Python now"]}),
|
||||
json.dumps({"memory": [{"id": "0", "text": "The user loves Python", "event": "UPDATE", "old_memory": "User likes Python"}]}),
|
||||
]
|
||||
mock_llm.generate_response.return_value = json.dumps(
|
||||
{"memory": [{"text": "The user loves Python"}]}
|
||||
)
|
||||
mock_llm_factory.return_value = mock_llm
|
||||
|
||||
# embed_batch is used in Phase 3 for all extracted memories
|
||||
embedder.embed_batch.return_value = [[0.4, 0.5, 0.6]]
|
||||
|
||||
mock_sqlite.return_value = MagicMock()
|
||||
|
||||
from mem0.memory.main import Memory as MemoryClass
|
||||
@@ -567,12 +570,11 @@ def test_update_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm
|
||||
|
||||
memory.add("I love Python now", user_id="test_user", infer=True)
|
||||
|
||||
# embed should be called exactly twice:
|
||||
# 1. For the extracted fact "User loves Python now" (search)
|
||||
# 2. For the rewritten text "The user loves Python" (pre-cached before _update_memory)
|
||||
# It should NOT be called a 3rd time inside _update_memory
|
||||
assert embedder.embed.call_count == 2
|
||||
mock_vector_store.update.assert_called_once()
|
||||
# V3 pipeline: embed called once for search query (Phase 1),
|
||||
# embed_batch called once for extracted memories (Phase 3)
|
||||
assert embedder.embed.call_count == 1
|
||||
assert embedder.embed_batch.call_count == 1
|
||||
mock_vector_store.insert.assert_called_once()
|
||||
|
||||
|
||||
@patch('mem0.utils.factory.EmbedderFactory.create')
|
||||
@@ -761,30 +763,6 @@ class TestProcessMetadataFiltersMerge:
|
||||
|
||||
# --- Issue #3040: reset() should clean up graph database ---
|
||||
|
||||
@patch('mem0.utils.factory.EmbedderFactory.create')
|
||||
@patch('mem0.utils.factory.VectorStoreFactory.create')
|
||||
@patch('mem0.utils.factory.LlmFactory.create')
|
||||
@patch('mem0.memory.storage.SQLiteManager')
|
||||
def test_reset_calls_graph_reset_when_graph_enabled(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory):
|
||||
"""Test that reset() calls graph.reset() when graph is enabled (issue #3040)."""
|
||||
mock_embedder_factory.return_value = MagicMock()
|
||||
mock_vector_store = MagicMock()
|
||||
mock_vector_factory.return_value = mock_vector_store
|
||||
mock_llm_factory.return_value = MagicMock()
|
||||
mock_sqlite.return_value = MagicMock()
|
||||
|
||||
config = MemoryConfig()
|
||||
memory = Memory(config)
|
||||
|
||||
# Simulate graph being enabled by setting graph instance
|
||||
mock_graph = MagicMock()
|
||||
memory.graph = mock_graph
|
||||
|
||||
memory.reset()
|
||||
|
||||
mock_graph.reset.assert_called_once()
|
||||
|
||||
|
||||
@patch('mem0.utils.factory.EmbedderFactory.create')
|
||||
@patch('mem0.utils.factory.VectorStoreFactory.create')
|
||||
@patch('mem0.utils.factory.LlmFactory.create')
|
||||
@@ -807,28 +785,3 @@ def test_reset_skips_graph_when_graph_disabled(mock_sqlite, mock_llm_factory, mo
|
||||
|
||||
# graph should remain None after reset
|
||||
assert memory.graph is None
|
||||
|
||||
|
||||
@patch('mem0.utils.factory.EmbedderFactory.create')
|
||||
@patch('mem0.utils.factory.VectorStoreFactory.create')
|
||||
@patch('mem0.utils.factory.LlmFactory.create')
|
||||
@patch('mem0.memory.storage.SQLiteManager')
|
||||
def test_reset_continues_if_graph_reset_fails(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory):
|
||||
"""Test that reset() doesn't crash if graph.reset() raises an exception (issue #3040)."""
|
||||
mock_embedder_factory.return_value = MagicMock()
|
||||
mock_vector_store = MagicMock()
|
||||
mock_vector_factory.return_value = mock_vector_store
|
||||
mock_llm_factory.return_value = MagicMock()
|
||||
mock_sqlite.return_value = MagicMock()
|
||||
|
||||
config = MemoryConfig()
|
||||
memory = Memory(config)
|
||||
|
||||
mock_graph = MagicMock()
|
||||
mock_graph.reset.side_effect = Exception("Neo4j connection failed")
|
||||
memory.graph = mock_graph
|
||||
|
||||
# Should NOT raise — graph failure is logged but reset continues
|
||||
memory.reset()
|
||||
|
||||
mock_graph.reset.assert_called_once()
|
||||
|
||||
Reference in New Issue
Block a user