mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-16 23:41:12 +00:00
The pipecat, cartesia, and agent-framework packages all pass search results from the Supermemory SDK (pydantic models, attribute access, snake_case fields) into dedup/format helpers written for plain dicts. The consequences differed by package but all killed the feature's primary path: - pipecat: deduplicate_memories called r.get() on a Result model -> AttributeError -> the outer handler logs "Error processing frame" and forwards the frame unchanged, so memories are never injected for any user whose profile lookup returns search results - cartesia: identical crash, swallowed by _enrich_event_with_memories' generic except -> memory_context silently comes back empty and the previous memory block is stripped from the system prompt - agent-framework: extract_memory_text only handled dict/str, so model objects fell through to `return None` and every search-result memory was silently dropped from the injected context (The OpenAI package shares the same helper shape but works because it fetches profile data over raw aiohttp and receives dicts — that asymmetry is what hid this.) pipecat/cartesia gain an extract_search_result_fields helper that reads memory/updatedAt off dicts (camelCase keys) or SDK models (snake_case attributes, datetime-tolerant), used by both deduplicate_memories and format_memories_to_text; the formatter also no longer prints raw object reprs for non-dict items. agent-framework's extract_memory_text gains a model-object branch. Verified against the real SDK: built a supermemory.types.search_memories_response.Result from an API-shaped payload and ran it through dedup -> extract -> format (crashed with AttributeError before, renders "- [1 Jan] User prefers async" after). Test suites: pipecat 7 passed, cartesia 7 passed, agent-framework 56 passed (agent-framework-core pinned to 1.0.0rc3 — newer releases have dropped BaseContextProvider, which breaks the package import independently of this change).
87 lines
3 KiB
Python
87 lines
3 KiB
Python
"""Regression tests for SDK model-object search results.
|
|
|
|
The Supermemory SDK returns search results as pydantic models (attribute
|
|
access), not dicts. deduplicate_memories used to call r.get() on them,
|
|
raising AttributeError and killing memory injection for any user whose
|
|
profile lookup returned search results.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from datetime import datetime, timezone
|
|
from types import SimpleNamespace
|
|
|
|
from supermemory_pipecat.utils import (
|
|
deduplicate_memories,
|
|
extract_search_result_fields,
|
|
format_memories_to_text,
|
|
)
|
|
|
|
|
|
def _model(memory, updated_at=None):
|
|
"""Stand-in for an SDK pydantic Result: attribute access, no .get()."""
|
|
return SimpleNamespace(memory=memory, updated_at=updated_at)
|
|
|
|
|
|
class TestModelObjectSearchResults(unittest.TestCase):
|
|
def test_deduplicates_model_objects_without_crashing(self):
|
|
results = [
|
|
_model("User likes Python"),
|
|
_model("User likes Python"),
|
|
_model("User works remotely"),
|
|
]
|
|
deduped = deduplicate_memories(static=[], dynamic=[], search_results=results)
|
|
memories = [
|
|
extract_search_result_fields(r)[0] for r in deduped["search_results"]
|
|
]
|
|
self.assertEqual(memories, ["User likes Python", "User works remotely"])
|
|
|
|
def test_profile_entries_still_win_over_model_search_results(self):
|
|
deduped = deduplicate_memories(
|
|
static=["User likes Python"],
|
|
dynamic=[],
|
|
search_results=[
|
|
_model("User likes Python"),
|
|
_model("User prefers async"),
|
|
],
|
|
)
|
|
self.assertEqual(deduped["static"], ["User likes Python"])
|
|
self.assertEqual(
|
|
[extract_search_result_fields(r)[0] for r in deduped["search_results"]],
|
|
["User prefers async"],
|
|
)
|
|
|
|
def test_format_renders_memory_text_not_object_repr(self):
|
|
deduped = deduplicate_memories(
|
|
static=[], dynamic=[], search_results=[_model("User prefers async")]
|
|
)
|
|
text = format_memories_to_text(deduped)
|
|
self.assertIn("- User prefers async", text)
|
|
self.assertNotIn("namespace", text)
|
|
|
|
def test_dict_results_keep_working(self):
|
|
deduped = deduplicate_memories(
|
|
static=[],
|
|
dynamic=[],
|
|
search_results=[
|
|
{"memory": "From a dict", "updatedAt": "2026-01-01T00:00:00Z"}
|
|
],
|
|
)
|
|
text = format_memories_to_text(deduped)
|
|
self.assertIn("From a dict", text)
|
|
|
|
def test_extract_handles_datetime_updated_at(self):
|
|
item = _model("x", updated_at=datetime(2026, 1, 1, tzinfo=timezone.utc))
|
|
memory, updated_at = extract_search_result_fields(item)
|
|
self.assertEqual(memory, "x")
|
|
self.assertTrue(updated_at.startswith("2026-01-01"))
|
|
|
|
def test_extract_tolerates_missing_fields(self):
|
|
memory, updated_at = extract_search_result_fields(SimpleNamespace())
|
|
self.assertEqual(memory, "")
|
|
self.assertEqual(updated_at, "")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|