This commit is contained in:
+9
-2
@@ -22,6 +22,7 @@ from mem0.configs.prompts import (
|
|||||||
PROCEDURAL_MEMORY_SYSTEM_PROMPT,
|
PROCEDURAL_MEMORY_SYSTEM_PROMPT,
|
||||||
generate_additive_extraction_prompt,
|
generate_additive_extraction_prompt,
|
||||||
)
|
)
|
||||||
|
from mem0.exceptions import LLMError
|
||||||
from mem0.exceptions import ValidationError as Mem0ValidationError
|
from mem0.exceptions import ValidationError as Mem0ValidationError
|
||||||
from mem0.memory.base import MemoryBase
|
from mem0.memory.base import MemoryBase
|
||||||
from mem0.memory.setup import mem0_dir, setup_config
|
from mem0.memory.setup import mem0_dir, setup_config
|
||||||
@@ -912,8 +913,12 @@ class Memory(MemoryBase):
|
|||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Re-raise so callers can implement provider fallback / retry.
|
||||||
|
# The original silent ``return []`` made upstream callers unable to
|
||||||
|
# distinguish "LLM unavailable" (429/5xx/timeout) from "LLM
|
||||||
|
# extracted no facts" -- both surfaced as an empty list.
|
||||||
logger.error(f"LLM extraction failed: {e}")
|
logger.error(f"LLM extraction failed: {e}")
|
||||||
return []
|
raise LLMError(f"LLM extraction failed: {e}") from e
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
try:
|
try:
|
||||||
@@ -2536,8 +2541,10 @@ class AsyncMemory(MemoryBase):
|
|||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Re-raise so callers can implement provider fallback / retry
|
||||||
|
# (see sync counterpart for rationale).
|
||||||
logger.error(f"LLM extraction failed (async): {e}")
|
logger.error(f"LLM extraction failed (async): {e}")
|
||||||
return []
|
raise LLMError(f"LLM extraction failed: {e}") from e
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from unittest.mock import MagicMock, Mock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from mem0.exceptions import LLMError
|
||||||
from mem0.memory.main import AsyncMemory, Memory
|
from mem0.memory.main import AsyncMemory, Memory
|
||||||
|
|
||||||
|
|
||||||
@@ -79,6 +80,28 @@ class TestAddToVectorStoreErrors:
|
|||||||
assert mock_memory.llm.generate_response.call_count == 1
|
assert mock_memory.llm.generate_response.call_count == 1
|
||||||
assert result == [] # Should return empty list when no memories processed
|
assert result == [] # Should return empty list when no memories processed
|
||||||
|
|
||||||
|
def test_llm_extraction_exception_is_reraised(self, mocker, mock_memory):
|
||||||
|
"""A provider error during fact extraction must propagate, not be swallowed.
|
||||||
|
|
||||||
|
Regression guard for the silent ``return []`` that made it impossible for
|
||||||
|
callers to tell "LLM unavailable" (429/5xx/timeout) from "no facts found".
|
||||||
|
Without the fix this raises AssertionError because the call returns [].
|
||||||
|
"""
|
||||||
|
|
||||||
|
class _ProviderError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_memory.llm.generate_response.side_effect = _ProviderError("429 rate limit")
|
||||||
|
mocker.patch("mem0.memory.main.capture_event")
|
||||||
|
|
||||||
|
with pytest.raises(LLMError) as exc_info:
|
||||||
|
mock_memory._add_to_vector_store(
|
||||||
|
messages=[{"role": "user", "content": "test"}], metadata={}, filters={}, infer=True
|
||||||
|
)
|
||||||
|
# The documented LLMError contract is honoured, and the original
|
||||||
|
# provider exception is preserved as the cause for debugging.
|
||||||
|
assert isinstance(exc_info.value.__cause__, _ProviderError)
|
||||||
|
|
||||||
|
|
||||||
class TestPromptOverridesCustomInstructions:
|
class TestPromptOverridesCustomInstructions:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -245,6 +268,29 @@ class TestAsyncAddToVectorStoreErrors:
|
|||||||
assert result == []
|
assert result == []
|
||||||
assert mock_async_memory.llm.generate_response.call_count == 1
|
assert mock_async_memory.llm.generate_response.call_count == 1
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_async_llm_extraction_exception_is_reraised(self, mock_async_memory, mocker):
|
||||||
|
"""Async counterpart of the sync re-raise guard.
|
||||||
|
|
||||||
|
A provider error during fact extraction must propagate as ``LLMError``
|
||||||
|
(with the original exception preserved as the cause), not be swallowed
|
||||||
|
into ``return []``. Without the fix a future revert of the async
|
||||||
|
``raise`` back to ``return []`` would pass the suite silently.
|
||||||
|
"""
|
||||||
|
mocker.patch("mem0.utils.factory.EmbedderFactory.create", return_value=MagicMock())
|
||||||
|
|
||||||
|
class _ProviderError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_async_memory.llm.generate_response.side_effect = _ProviderError("429 rate limit")
|
||||||
|
mocker.patch("mem0.memory.main.capture_event")
|
||||||
|
|
||||||
|
with pytest.raises(LLMError) as exc_info:
|
||||||
|
await mock_async_memory._add_to_vector_store(
|
||||||
|
messages=[{"role": "user", "content": "test"}], metadata={}, effective_filters={}, infer=True
|
||||||
|
)
|
||||||
|
assert isinstance(exc_info.value.__cause__, _ProviderError)
|
||||||
|
|
||||||
|
|
||||||
def _build_memory_instance(mocker, memory_cls):
|
def _build_memory_instance(mocker, memory_cls):
|
||||||
_setup_mocks(mocker)
|
_setup_mocks(mocker)
|
||||||
|
|||||||
Reference in New Issue
Block a user