From 59e8329203a95e26a0dcec338a85e1157d575c95 Mon Sep 17 00:00:00 2001 From: Pawan Date: Wed, 24 Jun 2026 23:32:31 +0530 Subject: [PATCH] fix(cartesia-sdk): treat empty profile as no memories, not a retrieval error A user with no stored memories gets profile=None from the API. _retrieve_memories read response.profile.static/.dynamic without a None check, so it raised AttributeError for those users, surfaced as MemoryRetrievalError. Every new or empty-profile user then triggered a false "Memory retrieval failed" log and got no memory injection. Return empty memories instead, matching the pipecat SDK fix in #1027/#1033. Adds a regression test. --- .../src/supermemory_cartesia/agent.py | 20 +++-- .../cartesia-sdk-python/tests/__init__.py | 0 .../tests/test_empty_profile.py | 77 +++++++++++++++++++ 3 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 packages/cartesia-sdk-python/tests/__init__.py create mode 100644 packages/cartesia-sdk-python/tests/test_empty_profile.py diff --git a/packages/cartesia-sdk-python/src/supermemory_cartesia/agent.py b/packages/cartesia-sdk-python/src/supermemory_cartesia/agent.py index 3d209aa00..9019193e8 100644 --- a/packages/cartesia-sdk-python/src/supermemory_cartesia/agent.py +++ b/packages/cartesia-sdk-python/src/supermemory_cartesia/agent.py @@ -166,20 +166,26 @@ async def _retrieve_memories(self, query: str) -> Dict[str, Any]: timeout=10.0 ) - static_count = len(response.profile.static) if response.profile.static else 0 - dynamic_count = len(response.profile.dynamic) if response.profile.dynamic else 0 - search_count = len(response.search_results.results) if response.search_results and response.search_results.results else 0 - - logger.info(f"[Supermemory] Retrieved memories - static: {static_count}, dynamic: {dynamic_count}, search: {search_count}") + # A user with no stored memories yet gets a null profile back, which + # is a normal case, not an error. Guard against it so we return an + # empty profile instead of raising AttributeError on response.profile. + profile = getattr(response, "profile", None) + profile_static = profile.static if profile is not None and profile.static else [] + profile_dynamic = profile.dynamic if profile is not None and profile.dynamic else [] search_results = [] if response.search_results and response.search_results.results: search_results = response.search_results.results + logger.info( + f"[Supermemory] Retrieved memories - static: {len(profile_static)}, " + f"dynamic: {len(profile_dynamic)}, search: {len(search_results)}" + ) + return { "profile": { - "static": response.profile.static or [], - "dynamic": response.profile.dynamic or [], + "static": profile_static, + "dynamic": profile_dynamic, }, "search_results": search_results, } diff --git a/packages/cartesia-sdk-python/tests/__init__.py b/packages/cartesia-sdk-python/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/cartesia-sdk-python/tests/test_empty_profile.py b/packages/cartesia-sdk-python/tests/test_empty_profile.py new file mode 100644 index 000000000..382e5e5f6 --- /dev/null +++ b/packages/cartesia-sdk-python/tests/test_empty_profile.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import sys +import types +import unittest +from types import SimpleNamespace +from unittest.mock import AsyncMock + + +def _install_test_stubs() -> None: + if "loguru" not in sys.modules: + loguru_module = types.ModuleType("loguru") + + class _Logger: + def info(self, *_args, **_kwargs): + return None + + def warning(self, *_args, **_kwargs): + return None + + def error(self, *_args, **_kwargs): + return None + + loguru_module.logger = _Logger() + sys.modules["loguru"] = loguru_module + + if "pydantic" not in sys.modules: + pydantic_module = types.ModuleType("pydantic") + + class BaseModel: + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + def Field(*, default=None, **_kwargs): + return default + + pydantic_module.BaseModel = BaseModel + pydantic_module.Field = Field + sys.modules["pydantic"] = pydantic_module + + +_install_test_stubs() + +from supermemory_cartesia.agent import SupermemoryCartesiaAgent + + +class _MockSupermemoryClient: + def __init__(self, response): + self.profile = AsyncMock(return_value=response) + + +class TestSupermemoryCartesiaNullProfile(unittest.IsolatedAsyncioTestCase): + async def test_retrieve_memories_handles_null_profile(self) -> None: + agent = SupermemoryCartesiaAgent( + agent=SimpleNamespace(), + api_key="mock_key", + container_tag="user-123", + custom_id="conversation-456", + ) + + response = SimpleNamespace(profile=None, search_results=None) + agent._supermemory_client = _MockSupermemoryClient(response) + + result = await agent._retrieve_memories("Hello world") + + self.assertEqual( + result, + { + "profile": {"static": [], "dynamic": []}, + "search_results": [], + }, + ) + + +if __name__ == "__main__": + unittest.main()